elements
Demos UI Feed Discuss Install
Style System
  • Style
  • Themes
  • Behaviors
Forms
  • Button
  • ButtonGroup
  • Checkbox
  • ColorPicker
  • Combobox
  • Field
  • FileUpload
  • Input
  • InputGroup
  • InputOTP
  • Label
  • NativeSelect
  • NumberInput
  • PasswordInput
  • RadioGroup
  • Select
  • Slider
  • Switch
  • TagsInput
  • Textarea
  • Toggle
  • ToggleGroup
Overlays
  • AlertDialog
  • Command
  • ContextMenu
  • Dialog
  • Drawer
  • DropdownMenu
  • HoverCard
  • Menubar
  • NavigationMenu
  • Popover
  • Sheet
  • Toast
  • Tooltip
Display
  • Accordion
  • Alert
  • AspectRatio
  • Avatar
  • Badge
  • Breadcrumb
  • Card
  • Collapsible
  • Empty
  • Item
  • Kbd
  • Pagination
  • Progress
  • Separator
  • Skeleton
  • Spinner
  • Table
  • Tabs
  • Tree
Layout
  • Carousel
  • Resizable
  • ScrollArea
  • Sidebar
Dates
  • Calendar
  • DatePicker
  • DateRangePicker
  • TimePicker
Data
  • AreaChart
  • BarChart
  • Chart
  • DataTable
  • Kanban
  • LineChart
  • PieChart
  • RadarChart
  • RadialChart
  • VirtualList
Chat
  • Attachment
  • Message
  • MessageList
  • PromptInput
Elements UI / Overlays / Tooltip

Tooltip

A short label for a control, shown on hover and keyboard focus.

Basic

Hover the button, or Tab to it. The tooltip describes the button and never takes focus.

Code
<Tooltip content="Add to library">
  <Button slot="trigger">Hover</Button>
</Tooltip>

Usage

import { Tooltip, TooltipProvider, TooltipTrigger } from "@elements/ui/tooltip";

<Tooltip content="Add to library">
  <Button slot="trigger" icon label="Add"><Icon name="plus" /></Button>
</Tooltip>

<!-- one delay for a region; the next tooltip opens at once -->
<TooltipProvider delay={300}>...</TooltipProvider>

Examples

Side

side picks where it opens; it flips when there is no room.

Code
<Tooltip side="left" content="Add to library">
  <Button slot="trigger">Left</Button>
</Tooltip>

RTL

inline-start and inline-end follow the text direction: in a right-to-left region inline-start shows on the right and inline-end on the left.

Code
<div dir="rtl">
  <Tooltip side="inline-start" content="أضف إلى المكتبة">
    <Button slot="trigger">بداية السطر</Button>
  </Tooltip>
  <Tooltip side="inline-end" content="أضف إلى المكتبة">
    <Button slot="trigger">نهاية السطر</Button>
  </Tooltip>
</div>

With a keyboard shortcut

Rich content goes in the default slot; a kbd reads on the tooltip's fill.

Saves to this device.
Code
<Tooltip>
  <Button slot="trigger" icon label="Save"><Icon name="download" /></Button>
  Save changes <kbd>S</kbd>
</Tooltip>

On a text field

A tooltip on a field in a form opens on keyboard focus and stays while the user types.

Code
<form>
  <Tooltip content="Letters, digits and spaces">
    <input slot="trigger" aria-label="Display name">
  </Tooltip>
</form>

Disabled button

A disabled button takes no pointer events or focus, so wrap it in a focusable span and put the tooltip on the span.

Code
<!-- a disabled button takes no pointer or focus: wrap it -->
<Tooltip content="You need edit access">
  <span slot="trigger" tabindex="0">
    <Button disabled>Disabled</Button>
  </span>
</Tooltip>

Delay group

TooltipProvider sets the delays for a region. Once one tooltip shows, the next opens at once as the pointer moves along.

Code
<TooltipProvider delay={500} skipDelay={300}>
  <Tooltip content="Bold"><Button slot="trigger" ...>...</Button></Tooltip>
  <Tooltip content="Italic">...</Tooltip>
  <Tooltip content="Underline">...</Tooltip>
</TooltipProvider>

No delay

delay={0} shows it the moment the pointer arrives.

Code
<Tooltip delay={0} content="No wait">
  <Button slot="trigger">Instant</Button>
</Tooltip>

Controlled and disabled

Bind open to state; onOpenChange reports the tooltip's own changes. disabled keeps it from opening.

open: false changes:
Code
<Tooltip open={state.tip} content="Controlled"
         onOpenChange={(open) => log(open)}>
  <Button slot="trigger">Target</Button>
</Tooltip>
<Tooltip disabled={state.off} content="...">...</Tooltip>

Follow the pointer

trackCursorAxis follows the pointer along x, y or both while it moves over the trigger.

Move along me
Move anywhere in me
Code
<Tooltip trackCursorAxis="x" delay={0} content="Follows the pointer">
  <div slot="trigger" class="track">Move along me</div>
</Tooltip>

<Tooltip trackCursorAxis="both" delay={0} content="Follows on both axes">
  <div slot="trigger" class="track-area">Move anywhere in me</div>
</Tooltip>

Not hoverable

By default the pointer can move from the button onto the tooltip. disableHoverableContent closes it as the pointer leaves the button.

Code
<Tooltip disableHoverableContent content="Closes as the pointer leaves the button">
  <Button slot="trigger">Not hoverable</Button>
</Tooltip>

Label and closing

label gives screen readers a different text than the visible one. closeOnClick={false} keeps it open when the trigger is pressed. closeDelay keeps it a moment after the pointer leaves; TooltipProvider sets it for a region.

Code
<Tooltip content="Delete" label="Delete the draft">
  <Button slot="trigger" icon label="Delete"><Icon name="trash" /></Button>
</Tooltip>
<Tooltip closeOnClick={false} content="Stays open on click">...</Tooltip>
<Tooltip closeDelay={500} content="Lingers for 500ms">...</Tooltip>
<TooltipProvider closeDelay={400}>...</TooltipProvider>

Align, offsets and anchor

align lines the tooltip up with the trigger's start, center or end; sideOffset and alignOffset move it; anchor positions it against another element.

Unsaved
Code
<Tooltip side="bottom" align="start" content="Lined up with the start">...</Tooltip>
<Tooltip side="bottom" align="start" sideOffset={12} alignOffset={8} content="Offset 12, 8">...</Tooltip>
<Tooltip anchor="#unsaved" content="Two files changed">
  <Button slot="trigger">Changes</Button>
</Tooltip>
<span id="unsaved">Unsaved</span>

Function offsets

sideOffset and alignOffset also take a function of the anchor's and the tooltip's sizes, the side and the alignment.

Code
<Tooltip side="bottom" sideOffset={(d) => d.anchor.height} content="One trigger height below">
  <Button slot="trigger">Offset</Button>
</Tooltip>

Collision boundary, sticky and arrow padding

collisionBoundary keeps the tooltip inside an element as well as the viewport. sticky="always" keeps it in the boundary after its trigger leaves. arrowPadding keeps the arrow off the corners.

Scroll this box while one is open.

Code
<div id="panel">
  <Tooltip side="bottom" align="start" collisionBoundary="#panel" content="...">...</Tooltip>
</div>

<Tooltip side="right" sticky="always" collisionBoundary="#list" content="...">...</Tooltip>
<Tooltip side="bottom" align="start" arrowPadding={40} content="...">...</Tooltip>

Change reasons

onOpenChange gets a second argument: the reason for the change, the event, and cancel() to keep the current state.

reasons:
Code
<!-- details.trigger is the trigger element that caused the change, if one did -->
<Tooltip content="Save" onOpenChange={(open, details) => log(open, details.reason, details.trigger)}>
  <Button slot="trigger">Save</Button>
</Tooltip>

<!-- details.cancel() keeps the current state -->
<Tooltip content="..." onOpenChange={(open, details) => details.reason === "escape-key" && details.cancel()}>...</Tooltip>

Hide when detached

hideWhenDetached hides the tooltip while its trigger is scrolled out of view.

Tab to the button, then scroll this box.

Code
<div class="scroller">
  <Tooltip hideWhenDetached content="Hidden while out of view">
    <Button slot="trigger">Focus me, then scroll</Button>
  </Tooltip>
</div>

Events

onOpenChangeComplete fires after each animation; onEscapeKeyDown and onPointerDownOutside fire before it closes, and preventDefault keeps it open.

complete: ; escapes: 0; outside: 0 kept:
Code
<Tooltip content="Counted" onOpenChangeComplete={(open) => log(open)}
         onEscapeKeyDown={(e) => count(e)}
         onPointerDownOutside={(e) => count(e)}>...</Tooltip>

<Button onclick={() => state.pinned = !state.pinned}>Pin tooltip</Button>
<Tooltip open={state.pinned} content="Pinned"
         onEscapeKeyDown={(e) => e.preventDefault()}
         onPointerDownOutside={(e) => e.preventDefault()}>...</Tooltip>

Collision avoidance and position method

collisionAvoidance tunes how it stays in view: side "none" keeps the side with no flip while align "shift" still slides it into view; side "shift" keeps the side and slides it over the trigger into view; fallbackAxisSide "start" tries the inline start side before the end. positionMethod="absolute" places it in page coordinates.

Code
<Tooltip side="top" collisionAvoidance={{ side: "none", align: "shift" }} content="...">...</Tooltip>
<Tooltip side="top" collisionAvoidance={{ side: "shift" }} content="...">...</Tooltip>
<Tooltip side="top" collisionAvoidance={{ fallbackAxisSide: "start" }}>...</Tooltip>
<Tooltip side="bottom" positionMethod="absolute" content="...">...</Tooltip>

Collisions

collisionPadding sets the room kept from the viewport edge; avoidCollisions={false} keeps it on its side with no flip or shift. TooltipProvider disableHoverableContent applies to every tooltip inside.

Code
<Tooltip side="bottom" align="end" collisionPadding={24} content="...">...</Tooltip>
<Tooltip side="left" avoidCollisions={false} content="...">...</Tooltip>
<TooltipProvider disableHoverableContent>...</TooltipProvider>

Multiple triggers

TooltipTrigger with a handle shows one tooltip for several triggers. It sits against the trigger under the pointer and moves along with it; payload carries that trigger's data, and only that trigger is described by it. Tab between them and PopoverViewport cross-fades the label.

Code
<TooltipTrigger handle="format" payload="Bold"><Button icon label="Bold">B</Button></TooltipTrigger>
<TooltipTrigger handle="format" payload="Italic"><Button icon label="Italic">I</Button></TooltipTrigger>

<!-- open, triggerId and payload are two-way: set triggerId and open to show it on a chosen trigger -->
<Tooltip handle="format" open={state.open} triggerId={state.trigger} payload={state.tool}>
  <PopoverViewport>{state.tool}</PopoverViewport>
</Tooltip>

Keep mounted

keepMounted keeps the closed tooltip in the page, hidden, rather than removing it.

Kept in the page
Code
<Tooltip content="Kept in the page" keepMounted>
  <Button slot="trigger">Kept</Button>
</Tooltip>

Disable anchor tracking

disableAnchorTracking places the tooltip once when it opens; it stays where it is while the page scrolls.

Code
<Tooltip content="Placed once" disableAnchorTracking>
  <Button slot="trigger">Pinned in place</Button>
</Tooltip>

Open on load

open={true} without a binding: the tooltip shows when the page loads, then owns its state.

Code
<Tooltip open={true} content="Open on load">
  <Button slot="trigger">Tip</Button>
</Tooltip>
Reference Tooltip in the manual Every part, attribute, slot, key and data attribute.
Prev ToastNext Accordion
Product
Manual Pricing Compare
Company
History Contact
Legal
Privacy Policy Acceptable Use Terms License Agreement
© Elements Dev Corporation 2026