Overlays & menus
Tooltip
Appears from its origin direction
detent-tooltip
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-overlays
- Group
- Overlays & menus
5.5 KB gzip for the class module · inlines the shared spring
Variants
Recorded states
hoverrecorded state; no setteropenrecorded state; no setterdark themerecorded state; no setter
Usage
Import
import '/components/tooltip/define.js';
// or register it yourself
import { DetentTooltip } from '/components/tooltip/tooltip.js';
Attributes
aligndelay-durationforce-openoffsetshow-arrowside
Properties
No properties are recorded for this component.
Slots
contentdefault
Events
No events are recorded for this component.
Example
Runnable example: site/examples/tooltip.ts. The snippet below is generated from the catalogued attributes.
<detent-tooltip
align="center"
delay-duration="150"
force-open
offset="8"
show-arrow
side="top"
></detent-tooltip>
Source
The pinned upstream source for this row:
src/components/ui/DirectionalTooltip.tsxsha256 bcce99ca9496…
Native files
The native module and its artifacts:
src/components/tooltip/tooltip.tssrc/components/tooltip/styles.csssrc/components/tooltip/define.tsdocs/components/tooltip.mdsite/examples/tooltip.tstests/browser/tooltip.spec.tsevidence/components/tooltip.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
align | align | 'start' | 'center' | 'end' | 'center' | Kebab-case attribute; camelCase property. |
delay-duration | delayDuration | number | 150 | Kebab-case attribute; camelCase property. |
force-open | forceOpen | boolean | false | Presence semantics: present means true, absent means false. |
offset | offset | number | 8 | Kebab-case attribute; camelCase property. |
show-arrow | showArrow | boolean | true | Presence semantics: present means true, absent means false. |
side | side | 'top' | 'right' | 'bottom' | 'left' | 'top' | Kebab-case attribute; camelCase property. |
Properties
No property-only values are recorded for this component.
Events
No custom events are recorded for this component.
Slots
| Slot | Notes |
|---|---|
content | Consumer content; rendered through a slot, never re-created. |
default | Consumer content; rendered through a slot, never re-created. |
CSS customisation
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- The trigger is never cloned. Upstream React.cloneElementd the child to inject refs and handlers; this port listens on the host and leaves the consumer's node, listeners and focus untouched (PC-11.2/11.6). children/className are the default slot and documented parts/custom properties.
- React nodes become slots. content is slot="content"; content is text or consumer DOM, never parsed markup (PC-11.1).
- A manual popover replaces position: absolute. The tooltip is in the top layer, so ancestor overflow/transform cannot clip or offset it (PC-9.8); the upstream absolute placement could be clipped.
- Placement is computed and clamped. Upstream only applied the preferred side and alignment (its "may be flipped" note was not implemented) and its centred position could overflow the viewport; this port flips to the opposite side when the preferred one has no room and clamps to an 8 px margin.
- The arrow is a clipped caret in the muted text token instead of a border-4 triangle drawn with borderTopColor: var(--border); it is decorative and aria-hidden (PC-11.5).
- Description wiring is implemented. Upstream set aria-describedby to a useId inside the same React tree; a shadow-DOM id cannot be referenced from the document, so the slotted content element (or aria-description for text-only content) is the description target, and consumer values are restored on close.
- Escape dismisses the tooltip (WCAG 1.4.13). Upstream had no dismissal path at all; only the most recently opened tooltip handles the key so stacked tooltips do not all close at once.
- The spring is the shared helper with the pinned springSnappy values; the exit animation is dropped (the tooltip hides immediately so the pointer target cannot linger), and reduced motion writes the settled state with no frames (PC-10.2/10.5).
- white-space: nowrap became wrapping text with max-width: 20rem and overflow-wrap: break-word, so long tooltips stay inside the viewport and remain readable at narrow widths (PC-9.6).
What it does
A tooltip that opens toward the side with room, reached by hover or keyboard focus and rendered above everything so it is never clipped.
Upstream behaviour
- Entry motion originates from the chosen side (top/right/bottom/left)
- Snappy spring (springSnappy) settles the tooltip into place
- Alignment per side: start / center / end
- Configurable delay, offset, and optional arrow indicator
- Light/dark theme aware via CSS variables
Differences from upstream
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- The trigger is never cloned. Upstream React.cloneElementd the child to inject refs and handlers; this port listens on the host and leaves the consumer's node, listeners and focus untouched (PC-11.2/11.6). children/className are the default slot and documented parts/custom properties.
- React nodes become slots. content is slot="content"; content is text or consumer DOM, never parsed markup (PC-11.1).
- A manual popover replaces position: absolute. The tooltip is in the top layer, so ancestor overflow/transform cannot clip or offset it (PC-9.8); the upstream absolute placement could be clipped.
- Placement is computed and clamped. Upstream only applied the preferred side and alignment (its "may be flipped" note was not implemented) and its centred position could overflow the viewport; this port flips to the opposite side when the preferred one has no room and clamps to an 8 px margin.
- The arrow is a clipped caret in the muted text token instead of a border-4 triangle drawn with borderTopColor: var(--border); it is decorative and aria-hidden (PC-11.5).
- Description wiring is implemented. Upstream set aria-describedby to a useId inside the same React tree; a shadow-DOM id cannot be referenced from the document, so the slotted content element (or aria-description for text-only content) is the description target, and consumer values are restored on close.
- Escape dismisses the tooltip (WCAG 1.4.13). Upstream had no dismissal path at all; only the most recently opened tooltip handles the key so stacked tooltips do not all close at once.
- The spring is the shared helper with the pinned springSnappy values; the exit animation is dropped (the tooltip hides immediately so the pointer target cannot linger), and reduced motion writes the settled state with no frames (PC-10.2/10.5).
- white-space: nowrap became wrapping text with max-width: 20rem and overflow-wrap: break-word, so long tooltips stay inside the viewport and remain readable at narrow widths (PC-9.6).