Tooltip
A hover or focus hint.
Anatomy
<script>
import { Tooltip } from '@shardsui/svelte/tooltip'
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger />
<Tooltip.Portal>
<Tooltip.Positioner>
<Tooltip.Popup>
<Tooltip.Viewport>
<!-- content -->
</Tooltip.Viewport>
<Tooltip.Arrow />
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>Usage guidelines
- Prefer using tooltips as visual labels only: alone they are not accessible to touch or screen reader users.
- Provide an accessible name for the trigger: a tooltip is visual-only and doesn't label its trigger. Give the trigger an
aria-labelthat closely matches the tooltip's content so screen reader users get a consistent name.
Alternatives to tooltips
Without a hover-capable pointer, there's no discoverable way to surface a tooltip before tapping its trigger. Long press isn't a way out either: iOS has no system-standard tooltip affordance, and Android's long-press gesture is already claimed by the browser's contextual menus. So tooltips stay disabled on touch devices.
Infotips
For content that opens when hovering an info icon, use Popover with openOnHover on the trigger instead; that keeps the content reachable for touch and screen reader users. The test: when opening the overlay is the trigger's whole job, it's a popover; when the trigger does something else and the overlay is incidental, it's a tooltip.
Description text
When a description is essential to understanding an element, keep it out of a tooltip: put it in inline text, or in a Popover when space is tight, so everyone can read it. Save tooltips for non-essential hints, and make icon-only triggers legible on their own, especially on mobile where the tooltip's text label never shows.
Contextual feedback messages
For feedback tied to a specific control, use Toast and its anchoring — it announces the message to screen readers and handles richer content.
Examples
Detached triggers
The trigger usually lives inside <Tooltip.Root>, next to the hint it shows, as in the hero demo above.
When the two can't share a spot in the markup, render <Tooltip.Trigger> wherever the element belongs and connect it to the root through a shared handle from new Tooltip.Handle().
The handle's imperative methods, open() and close(), need a <Tooltip.Root> using the same handle to be mounted — calls made while no root is attached (before one mounts, or after it unmounts) are ignored and not replayed. Each root starts from fresh state when it mounts; no open state carries over from a previous mount.
<script>
const demoTooltip = new Tooltip.Handle()
</script>
<Tooltip.Trigger handle={demoTooltip}>Button</Tooltip.Trigger>
<Tooltip.Root handle={demoTooltip}>...</Tooltip.Root>Multiple triggers
One tooltip can back several triggers. Give the same handle to a set of detached triggers, or list multiple <Tooltip.Trigger> elements inside a single <Tooltip.Root>.
<Tooltip.Root>
<Tooltip.Trigger>Trigger 1</Tooltip.Trigger>
<Tooltip.Trigger>Trigger 2</Tooltip.Trigger>
...
</Tooltip.Root>Each trigger can pass its own payload, letting one tooltip say something different per trigger. The children snippet on <Tooltip.Root> receives the active trigger's payload; add a type argument to new Tooltip.Handle() to type it:
<script>
const demoTooltip = new Tooltip.Handle<{ text: string }>()
</script>
<Tooltip.Trigger handle={demoTooltip} payload={{ text: 'Trigger 1' }}>Trigger 1</Tooltip.Trigger>
<Tooltip.Trigger handle={demoTooltip} payload={{ text: 'Trigger 2' }}>Trigger 2</Tooltip.Trigger>
<Tooltip.Root handle={demoTooltip}>
{#snippet children({ payload })}
<Tooltip.Portal>
<Tooltip.Positioner sideOffset={8}>
<Tooltip.Popup>
{#if payload}<span>Opened by {payload.text}</span>{/if}
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
{/snippet}
</Tooltip.Root>Controlled mode with multiple triggers
Drive the open state yourself with bind:open and onOpenChange on <Tooltip.Root>. Across multiple triggers, give each <Tooltip.Trigger> an id and add bind:triggerId to <Tooltip.Root>: each trigger publishes its own id when it opens the tooltip, and setting triggerId yourself anchors the tooltip to that trigger. Pass triggerId one-way instead if you want to drive it entirely from your own state.
Animating the Tooltip
A tooltip can travel smoothly from one trigger to the next instead of blinking off and on. Position, size, and content animate independently.
Position and Size
Position sits on the Positioner: transition left, right, top, and bottom. Size sits on the Popup: transition width and height.
Content
The content can shift too when triggers carry different hints. Wrap it in <Tooltip.Viewport>, which spots the trigger change and writes a data-activation-direction attribute — a space-separated horizontal and vertical pair such as right down, left, or up — indicating where the new trigger falls relative to the old one. Match a single token with the ~= attribute selector, such as [data-activation-direction~='right'].
Mid-transition, the viewport keeps both hints mounted, each in its own wrapper:
data-current: the entering content, or the sole content when nothing is transitioning. It also carriesdata-starting-stylewhile it animates in.data-previous: the leaving content during a transition. It also carriesdata-ending-stylewhile it animates out.
Target these to write the enter and exit animations.
API reference
Provider
Provides a shared delay for multiple tooltips. Once one tooltip is open, siblings open with no delay. Doesn't render its own HTML element.
Root
Groups all parts of the tooltip. Doesn't render its own HTML element.
Trigger
An element to attach the tooltip to.
Renders a <button> element.
Portal
A portal that moves the popup out to <body>, clear of ancestor clipping and stacking.
Renders a <div> element.
Positioner
Positions the tooltip against the trigger.
Renders a <div> element.
Popup
A container for the tooltip contents.
Renders a <div> element.
Arrow
Displays an element positioned against the anchor.
Renders a <div> element.
Viewport
A viewport for displaying content transitions.
This component is only required if one popup can be opened by multiple triggers, its content
changes based on the trigger, and switching between them is animated.
Renders a <div> element.
Reach for the Viewport only when a single popup is opened by multiple triggers, its content differs per trigger, and the switch between them is animated. When you do, set width: var(--positioner-width) and height: var(--positioner-height) on the Positioner so its box is frozen to the measured size during the transition; otherwise content-driven resizing can make the popup thrash or flip to another side.
Handle
Connects a <Tooltip.Root> with detached <Tooltip.Trigger> components, and controls the tooltip imperatively. Pass a type argument to type the payload.
const tooltip = new Tooltip.Handle<Payload>()