Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Components

Free

Toggle Tooltip for React Aria

Give an info icon a note that works on touch screens, not only on hover. For React with React Aria, when people need the text without hovering.

Framework
UI library

React Aria does not include this primitive, so this page uses the Ark UI component. The visuals match the Ark UI version.

Click or tap opens a short note, and a mouse hover can open it too.

Use it when the note has to work on a touch screen. A Tooltip is enough for a hint that only shows on hover or focus. In React, React Aria keeps the note open until it's dismissed.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Anatomy

Toggle Tooltip anatomy: content, arrow, trigger
ToggleTooltipArrow
└── ToggleTooltipArrowTip
ToggleTooltipAnchor
ToggleTooltipTrigger
ToggleTooltipIndicator
ToggleTooltipContent
└── ToggleTooltipPositioner
ToggleTooltipTitle
ToggleTooltipDescription
ToggleTooltipCloseTrigger

Usage

import { InfoIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
  ToggleTooltip, ToggleTooltipContent, ToggleTooltipTrigger,
} from "@/components/ui/toggle-tooltip";
<ToggleTooltip>
  <ToggleTooltipTrigger asChild>
    <Button aria-label="More information" size="icon-md" variant="outline">
      <InfoIcon />
    </Button>
  </ToggleTooltipTrigger>
  <ToggleTooltipContent className="max-w-52">
    Hover or click to see this content. Works on touch devices too.
  </ToggleTooltipContent>
</ToggleTooltip>

Accessibility

Enter
Activate the focused control.
Space
Activate the focused control.

Demos

Info Icon

Each row of a data list gets its own info button: New Users, Sales, and Revenue, with one sentence in the note. The button is named for that metric, because the icon alone does not say which number it describes. Put it beside a label when a definition would clutter the row, and keep the note short. A long explanation belongs on the page, not in the bubble.

Preview width

Side

Four triggers open the note on a different side: left, top, bottom, and right, via positioning and placement. Pin a side when the trigger sits in a corner or against a toolbar and the default would cover the thing it explains. The note can still flip if that side has no room. The content here is only the side name, so you can see the placement without reading a paragraph.

Preview width

The note holds a real link, which is the reason to prefer this over a hover tooltip. A tooltip closes when the pointer moves toward the link, so the click never lands. Here the note stays open until a second click, a click outside, or Escape. Keep the interactive bit small, and put a form in a Popover instead.

Preview width

FAQ

What is a Toggle Tooltip and when should I use it?

It is a tooltip that opens on click or tap instead of hover, so it also works on touch screens. Use it for an info icon next to a label or a setting; for hover hints on desktop, use a Tooltip.

What is the difference between a Toggle Tooltip, a Tooltip and a Popover?

A Tooltip shows on hover or focus and cannot hold interactive content. A Toggle Tooltip opens on click, stays until dismissed, and can hold a link, as in the With Link demo. A Popover is for larger content like a form.

How do I change where the Toggle Tooltip opens in React?

Pass positioning with a placement such as top, bottom, left or right, as the Side demo does. It flips to the other side when there is not enough room.

How do I open the Toggle Tooltip from code?

Keep the open state in state and pass it as open; use defaultOpen to start open. onOpenChange receives details.open.

How do I close the Toggle Tooltip?

It closes on a second click of its trigger, a click outside, or Escape. It does not trap focus or block the page, so people can keep working while it is open.

How do I make the Toggle Tooltip trigger accessible?

Use a Button as the trigger, with asChild, and give an icon-only Button an aria-label that says what it explains, like the Info Icon demo's "Info about" labels. The trigger is announced as a button that opens its content.

How do I install the Toggle Tooltip in React?

Run the command in the Installation section above: it adds the Toggle Tooltip to your ui folder. It is built on Ark UI's popover on every base and framework. The command uses the shadcn CLI.

API Reference

ToggleTooltip

data-slot="toggle-tooltip"

PropType
open?boolean

The controlled open state of the popover.

defaultOpen?boolean

The initial open state of the popover when rendered. Use when you don't need to control the open state of the popover.

onOpenChange?(details: OpenChangeDetails) → void

Function invoked when the popover opens or closes.

Type
(details: OpenChangeDetails) → void
Parameters

details – { open: boolean }

Returns
void
triggerValue?string | null

The controlled trigger value.

Type
string | null
defaultTriggerValue?string | null

The initial trigger value when rendered. Use when you don't need to control the trigger value.

Type
string | null
onTriggerValueChange?(details: TriggerValueChangeDetails) → void

Function called when the trigger value changes.

Type
(details: TriggerValueChangeDetails) → void
Parameters

details – { value: string | null; triggerElement: HTMLElement | null }

Returns
void
autoFocus?boolean

Whether to automatically set focus on the first focusable content within the popover when opened.

Default
true
closeOnEscape?boolean

Whether to close the popover when the escape key is pressed.

Default
true
closeOnInteractOutside?boolean

Whether to close the popover when the user clicks outside of the popover.

Default
true
dir?"ltr" | "rtl"

The document's text/writing direction.

Type
"ltr" | "rtl"
Default
"ltr"
finalFocusEl?() → MaybeElement

Element to receive focus when the popover is closed.

Returns
MaybeElement
hideMode?"display-none" | "activity"

How to hide content when mounted but not present. - display-none: HTML hidden attribute. Effects stay alive. - activity: React 19 <Activity mode="hidden">. Effects pause. Requires React 19+.

Type
"display-none" | "activity"
Default
"display-none"
id?string

The unique identifier of the machine.

ids?object

The ids of the elements in the popover. Useful for composition.

Type
{ anchor?: string; trigger?: string | ((value?: string) → string); content?: string; title?: string; description?: string; closeTrigger?: string; positioner?: string; arrow?: string }
initialFocusEl?() → HTMLElement | null | false

The element to focus on when the popover is opened. Return false to skip moving focus, or null to use the default.

Type
() → HTMLElement | null | false
Returns
HTMLElement | null | false
lazyMount?boolean

Whether to enable lazy mounting. Ark default: false.

Default
true
modal?boolean

Whether the popover should be modal. When set to true: - interaction with outside elements will be disabled - only popover content will be visible to screen readers - scrolling is blocked - focus is trapped within the popover

Default
false
onEscapeKeyDown?(event: KeyboardEvent) → void

Function called when the escape key is pressed.

Parameters

event – { altKey: boolean; charCode: number; ctrlKey: boolean; code: string; getModifierState: (key: string) → boolean; key: string; keyCode: number; locale: string; location: number; metaKey: boolean; repeat: boolean; shiftKey: boolean; which: number }

Returns
void
onFocusOutside?(event: FocusOutsideEvent) → void

Function called when the focus is moved outside the component.

Type
(event: FocusOutsideEvent) → void
Parameters

event – FocusOutsideEvent

Returns
void
onInteractOutside?(event: InteractOutsideEvent) → void

Function called when an interaction happens outside the component.

Type
(event: InteractOutsideEvent) → void
Parameters

event – InteractOutsideEvent

Returns
void
onPointerDownOutside?(event: PointerDownOutsideEvent) → void

Function called when the pointer is pressed down outside the component.

Type
(event: PointerDownOutsideEvent) → void
Parameters

event – PointerDownOutsideEvent

Returns
void
onRequestDismiss?(event: LayerDismissEvent) → void

Function called when this layer is closed due to a parent layer being closed.

Type
(event: LayerDismissEvent) → void
Parameters

event – LayerDismissEvent

Returns
void
persistentElements?Array<() → Element | null>

Returns the persistent elements that: - should not have pointer-events disabled - should not trigger the dismiss event

Type
Array<() → Element | null>
portalled?boolean

Whether the popover is portalled. This will proxy the tabbing behavior regardless of the DOM position of the popover content.

Default
true
positioning?object

The user provided options used to position the popover content.

Type
{ restoreStyles?: boolean; applyStyles?: boolean; hideWhenDetached?: boolean; strategy?: "absolute" | "fixed"; placement?: Placement; offset?: { mainAxis?: number; crossAxis?: number; }; gutter?: number; shift?: number; overflowPadding?: number; arrowPadding?: number; flip?: boolean | Placement[]; slide?: boolean; overlap?: boolean; sameWidth?: boolean; fitViewport?: boolean; sizeMiddleware?: boolean; boundary?: (() → Boundary) | Boundary | "clipping-ancestors"; listeners?: boolean | AutoUpdateOptions; onComplete?: (data: ComputePositionReturn) → void; onPositioned?: ((data: { placed: boolean; }) → void); getAnchorElement?: () → HTMLElement | VirtualElement | null; getAnchorRect?: (element: HTMLElement | VirtualElement | null) → AnchorRect | null; updatePosition?: ((data: { updatePosition: () → Promise<void>; floatingElement: HTMLElement | null; }) → void | Promise<void>) }
Default
{ placement: "top" }
restoreFocus?boolean

Whether to restore focus to the element that had focus before the popover was opened.

Default
true
skipAnimationOnMount?boolean

Whether to allow the initial presence animation.

Default
false
translations?object

Specifies the localized strings that identifies the accessibility elements and their states.

Type
{ closeTriggerLabel?: string }
unmountOnExit?boolean

Whether to unmount on exit. Ark default: false.

Default
true
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

ToggleTooltipArrow

<div> data-slot="toggle-tooltip-arrow"

PropType
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

CSS variableType
--arrow-backgroundstring

Use this variable to style the arrow background.

--arrow-offsetstring

The offset position of the arrow.

--arrow-sizestring

The size of the arrow.

--arrow-size-halfstring

Half the size of the arrow.

ToggleTooltipArrowTip

<div>

PropType
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

ToggleTooltipTrigger

<button> data-slot="toggle-tooltip-trigger"

PropType
value?string

The value that identifies this specific trigger.

asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

AttributeType
data-current?string

Present when current.

data-placement?string

The placement of the trigger.

data-side?string

The side of the trigger that the trigger is positioned on.

data-state"open" | "closed"

"open" or "closed".

Type
"open" | "closed"
data-value?string

The value of the item.

ToggleTooltipPositioner

<div> data-slot="toggle-tooltip-positioner"

PropType
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

AttributeType
data-has-nested?string

Present when the positioner has nested popovers.

data-nested?string

Present when the positioner is nested within another popover.

CSS variableType
--available-heightstring

The available height in viewport.

--available-widthstring

The available width in viewport.

--reference-heightstring

The height of the root.

--reference-widthstring

The width of the reference element.

--transform-originstring

The transform origin for animations.

--xstring

The x position for transform.

--ystring

The y position for transform.

--z-indexstring

The z-index value.

ToggleTooltipContent

<div> data-slot="toggle-tooltip-content"

PropType
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

className?string

Merged with the part's classes.

AttributeType
data-expanded?string

Present when expanded.

data-has-nested?string

Present when another popover is nested inside it.

data-nested?string

Present when it's nested inside another popover.

data-placementstring

The placement of the content.

data-side"top" | "right" | "bottom" | "left"

The side of the trigger that the content is positioned on.

Type
"top" | "right" | "bottom" | "left"
data-state"open" | "closed"

"open" or "closed".

Type
"open" | "closed"
CSS variableType
--layer-indexstring

The index of the dismissable in the layer stack.

--nested-layer-countstring

The number of nested popovers.

--transform-originstring

Where the content scales from, on its trigger's side.

With other components

  • Button

    Buttons

    The control

  • Toggle

    Selection

    A pressed state with a hint

  • Tooltip

    Overlays

    The same hint on hover

  • Kbd

    Typography

    The shortcut in the hint

  • Badge

    Feedback

    A mark on the control

  • Separator

    Layout

    Group the controls