Tooltip
A simple text prompt bubble triggered on hover or keyboard focus.
Basic usage
Trigger on hover
Placement
Four directions
When space is insufficient, the tooltip automatically flips along the main axis and avoids the viewport edges.
Arrow
By default an arrow pointing at the trigger element's edge is shown; arrow="false" hides the arrow; arrow-point-at-center makes the arrow point at the trigger element's center (when the panel is shifted by viewport-edge avoidance, the arrow still points at the anchor center).
Arrow visibility and pointing
arrow-position="merge" merges the arrow with the panel corner into a right triangle (only for *-start / *-end placements).
Arrow merge mode
Arrow shape is customized via CSS variable tokens (width/height independent — the aspect ratio is the triangle's height and angle):
Arrow shape tokens (width / height / align offset / radius)
--oas-tooltip-arrow-width / --oas-tooltip-arrow-height (default 12px each, falling back to the existing --oas-tooltip-arrow-size); --oas-tooltip-arrow-align-offset (offset on the *-start / *-end aligned side, default 16px — semantically different from the arrow-offset attribute of arrow-position="side", which is the side mode's edge inset); --oas-tooltip-arrow-radius (arrow border-radius, default 0). The merge form is an independent 8px geometry and is not affected by these tokens.
Viewport auto adjust
By default the tooltip automatically flips along the main axis and avoids the viewport edges when space is insufficient; auto-adjust-overflow="false" disables the auto adjust so the panel keeps the declared placement (it may overflow the viewport).
Auto adjust disabled
Focus trigger
Trigger on keyboard focus
Controlled display
The open attribute is controlled: an external button can set/remove open to show/hide the tooltip (hover/focus triggers still apply in addition).
Controlled display (open attribute)
Long text
Long text and max width
Virtual trigger
Virtual mode (virtual) does not bind to a host trigger element: open is fully controlled externally, and the position is set by virtual-anchor (an anchor element selector) or virtual-x / virtual-y (viewport coordinates). placement still applies. It suits scenarios where a normal trigger element is impossible, such as chart points or floating hints during drag.
Virtual anchor follow (chart points)
Hover any point to see the hint (the tooltip is anchored to that point).
Coordinate follow (mouse move)
Triggers
The trigger attribute supports space-separated multi-selection: hover / focus / click / contextmenu / touch (long press) / manual (fully controlled). Defaults to hover focus touch — long press works out of the box on touch screens (mouse pointers are filtered automatically; the hold duration is tuned by touch-delay). On coarse pointers (touch screens), hover automatically degrades to tap to toggle: tap to open, tap again or tap elsewhere to close (desktop hover behavior is unchanged).
Click trigger
Context menu trigger
Manual trigger
Show / hide delay
open-delay / close-delay control the delay (ms) before showing/hiding on hover, avoiding accidental triggers when moving across quickly. Keyboard focus opens immediately without waiting for open-delay (keyboard users have already positioned themselves; a wait adds no information).
Open and close delays
When moving quickly across multiple triggers, skip-delay-duration (default 300ms) makes the next tooltip skip its open-delay and appear immediately, keeping the interaction responsive.
Delay group (skip-delay-duration)
On touch devices a long press shows the hint (touch is included by default; touch-delay controls the hold duration, default 500ms; desktop mouse long presses are filtered).
Touch long-press trigger
Tap to toggle on touch (automatic on coarse pointers)
On touch devices (pointer: coarse) the hover channel is disabled — a tap synthesizes hover/focus/click events at once, and keeping desktop hover semantics would close the tip immediately after opening ("open-and-shut"). After degradation the tap takes over toggling, consistent with the click trigger: once open, tap anywhere outside (or press Esc) to close.
Rich content
The content attribute shows plain text; for rich content (links, icons, multiple lines) use the slot="content" slot. The slot takes precedence over the attribute text.
If the rich content holds interactive elements (links, etc.), combine interactive (hoverable popup — entering it keeps it open) with close-delay (time for the pointer to travel from the anchor into the popup) — this is also the recommended shape for WCAG 1.4.13 "hoverable".
Rich content slot
interactive + close-delay combo
Keyboard accessibility
While open, pressing Esc closes the tooltip and restores focus to the trigger; while open, the trigger is linked to the popup via aria-describedby (readable by screen readers).
Esc close + aria-describedby
trigger-keys lets you specify keys (space-separated) that open the tooltip when focused, e.g. trigger-keys="F1".
trigger-keys open
Max width
The default max width is 240px (opened via the --oas-tooltip-max-width token); the max-width attribute overrides it (number or CSS length).
Custom max width
Disabled
With disabled, the tooltip never shows (neither hover nor a controlled open).
Disabled
Hoverable popup
interactive keeps the popup open while hovering it (links inside stay reachable).
Interactive popup
Offset and collision
offset controls the main-axis distance (default 10px), skidding the cross-axis offset, and collision-padding the viewport-edge avoidance margin (default 4px).
offset / skidding
collision-padding
Color variants
The color attribute supports semantic colors (primary / success / warning / danger), the 11 preset names (e.g. magenta, blue), or any CSS color. All of them go through tokens (with dark variants).
Color variants
Mount point
append-to mounts the popup into the given container (body or a CSS selector), escaping clipping contexts such as an overflow: hidden ancestor.
append-to body
Auto close
auto-close (ms) closes the tooltip automatically after it opens — useful for onboarding hints.
Auto close
Fresh content
fresh is enabled by default: content changes are synced immediately even while closed (reopening always shows the latest). fresh="false" freezes the content while closed.
Fresh content sync
Scroll follow and close on scroll
While open, a normal anchor is also repositioned in real time on scroll / window resize (scroll capture + rAF throttling), so the popup no longer detaches from the anchor; close-on-scroll closes on scroll instead (for compact hints that should not chase the page).
Scroll follow
Scroll the box: while open, the tooltip follows the button
close-on-scroll
Open the hint by hovering, then scroll the container — it closes immediately
Click trigger with outside dismiss
When trigger includes click / contextmenu, pressing anywhere outside the popup and the trigger closes it (light dismiss) — no need to click the trigger again. Pure hover/focus/touch triggers or a controlled open do not install outside dismiss.
click + outside dismiss
Click to open, then click anywhere outside the popup / button (watch the status tag).
Accessible name semantics (label)
a11y switches the tooltip's semantic role: the default description puts aria-describedby on the trigger pointing to the popup (supplementary description); label uses aria-labelledby instead — for icon-only triggers without their own text, the hint text acts as the accessible name. On close the trigger's original aria value is restored, never clobbering host-set attributes.
label accessible name
While open the button gets aria-labelledby pointing to the popup (accessible name = hint text); it is restored on close.
Arrow side mode and arrow-offset
arrow-position="side" snaps the arrow to the half of the panel that contains the anchor-center projection (when the panel is shifted by viewport avoidance, the arrow hugs the matching side instead of staying centered). arrow-offset (px, only effective in side) controls the arrow's inset from the panel end, default 4px (a safe amount that keeps it out of the rounded corners).
Arrow side and offset
Scroll the trigger to a viewport edge before opening to see the side arrow hug the anchor side.
Animation customization (CSS variable hooks)
The entry animation duration / easing / name are CSS variables (set them on the <oas-tooltip> element — custom properties cascade into the shadow): --oas-tooltip-duration (default 0.15s), --oas-tooltip-easing (default ease), --oas-tooltip-animation (default oas-tooltip-in; none disables the entry animation). Under prefers-reduced-motion: reduce the animation is always off.
Duration / easing / animation toggle
Smart placement and collision boundary
placement="auto"(alsoauto-start/auto-end): picks the roomiest side on open;fallback-placements: a fallback sequence (space/comma-separated 12-direction placements) — when the primary side does not fit, the first fitting candidate wins;collision-boundary: swap the flip/avoid boundary from the viewport to a given element rect (e.g. a scroll container's visible area; pair withappend-toto avoid clipping);fallback-axis-side: cross-axis fallback when both main-axis sides are tight (start/end, defaultnone).
auto placement and fallback sequence
collision-boundary = scroll container
collision-boundary points at this container: the hint flips/avoids within its visible area
Cross-axis fallback (fallback-axis-side)
Follow cursor
follow-cursor: while open, moving the cursor inside the trigger area repositions the popup to follow the cursor in real time (reuses the viewport-coordinate channel, rAF throttled). Good for large hover surfaces.
follow-cursor
Width following the trigger
width="trigger" makes the popup as wide as the trigger (a number or CSS length sets the width directly); the width is still capped by --oas-tooltip-max-width (default 240px), which is adjustable.
width customization
Event source (oas-open-change)
oas-open-change's detail now carries { open, source, reason }: source is the trigger channel (hover / focus / click / contextmenu / touch / key / escape / outside / scroll / auto-close / attribute), reason refines the cause (e.g. escape-key / outside-pointer / timeout / long-press). External controlled setAttribute paths report attribute.
Event source feedback
Not triggered yet
Edge cases
Empty content
API
oas-tooltip
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
a11y | A11y association mode: description (default, the trigger gets aria-describedby pointing at the popup) / label (gets aria-labelledby as its accessible name); the host's original value is restored on close | string | description |
append-to | Popup mount point: body or a CSS selector. Moves the popup into an isolated shadow inside the target container (styles stay scoped), escaping clipping contexts such as overflow: hidden / transform; while mounted, ::part(tip) cannot pierce from the host — customize via CSS variables or class selectors | string | — |
arrow | Whether to show the arrow (default true; arrow="false" hides it, the element and ::part(arrow) are kept) | string | true |
arrow-offset | Arrow inset from the panel edge (px, default 4): only effective with arrow-position="side" (ignored for center/merge) | — | — |
arrow-point-at-center | Make the arrow point at the trigger element's center (default points at the trigger's edge; the arrow still points at the anchor center when the panel is shifted by viewport-edge avoidance) | boolean | — |
arrow-position | Arrow shape: center (default, arrow centered on the panel edge) / merge (only for *-start/*-end placements; a right triangle merges flush with the panel corner — legs collinear with the panel edges, tip pointing orthogonally toward the anchor) | string | center |
auto-adjust-overflow | Viewport-edge auto flip and avoidance (default true; "false" disables it, keeping the declared placement, which may overflow the viewport) | string | true |
auto-close | Auto-close after opening (ms); 0 or absent disables it | — | — |
close-delay | Hide delay (ms, default 0): close after mouseleave/focusout | — | — |
close-on-scroll | Close as soon as the page scrolls (listened while open, the first scroll hit closes; resize only repositions) | boolean | — |
collision-boundary | Collision boundary element selector: flips/clamps against this element rect instead of the viewport (the collisionBoundaryEl property channel takes precedence) | Element | null | — |
collision-padding | Viewport-edge avoidance margin (px, default 4): the distance kept from the edge when the popup is clamped | — | — |
color | Color variant: semantic primary/success/warning/danger, one of the 11 preset names (e.g. magenta, blue), or any CSS color. All go through tokens (with dark variants); the arrow background follows | string | — |
content | Tooltip content text (the slot="content" rich content takes precedence when present) | string | — |
disabled | Disabled: the tooltip never shows (neither hover nor a controlled open) | boolean | — |
fallback-axis-side | Cross-axis fallback when neither main-axis side fits: start (switch to the cross-axis start side, left/top) / end (right/bottom); none by default (keeps opposite-side flip + viewport clamping) | string | none |
fallback-placements | Custom fallback sequence (space/comma-separated 12-direction placements): when the requested placement does not fit, each entry is tried in order and the first fit wins; if none fit, the last entry is used with viewport clamping; unset uses the default main-axis flip | string | — |
follow-cursor | Follow the cursor: once open, positioned at the cursor coordinates (zero-size point); cursor movement is tracked with rAF throttling | boolean | — |
fresh | Content freshness (default true): content changes are synced immediately even while closed; "false" freezes the content while closed, updating on next open | string | true |
interactive | Hoverable popup: moving the mouse into the popup keeps it open (pointer-events: auto), links inside stay reachable | boolean | — |
max-width | Popup max width (number in px or CSS length; defaults to the --oas-tooltip-max-width token, 240px) | string | — |
offset | Main-axis distance (px, default 10): the gap between the popup and the anchor along the main axis | — | — |
open | Controlled display (boolean attribute; shows when present) | boolean | — |
open-delay | Show delay (ms, default 0): open after mouseenter/focusin; skipped when skip-delay-duration hits | — | — |
placement | Popup placement (12 directions: top/bottom/left/right × start/center/end) | string | top |
skidding | Cross-axis offset (px, default 0): top/bottom placements shift horizontally (positive right, negative left); left/right placements shift vertically (positive down, negative up) | — | — |
skip-delay-duration | Global delay-group threshold (ms, default 300): when a tooltip closes, the next one opened within this window skips its open-delay and shows immediately (responsive for consecutive hovers); "0" disables it | — | — |
touch-delay | Touch long-press trigger duration (ms, default 500): with touch in trigger, pointerdown held to the threshold opens; releasing/moving out earlier cancels | — | — |
trigger | Trigger modes (space-separated multi-select): hover / focus / click / contextmenu / touch / manual, default hover focus touch; manual is fully controlled | string | hover focus touch |
trigger-keys | Specified keys (space-separated, e.g. F1): pressing one while the trigger is focused opens the tooltip | string | — |
virtual | Virtual trigger mode: not bound to a host trigger element; open is fully controlled externally and the position is set by virtual-anchor or virtual-x/virtual-y (for chart points, floating hints during drag) | boolean | — |
virtual-anchor | Anchor element selector (e.g. #chart-point-1); the tooltip is positioned by that element's rect. Mutually exclusive with virtual-x/virtual-y (coordinates take precedence) | — | — |
virtual-x | Virtual anchor viewport X coordinate (px, e.g. mouse clientX); position by coordinates when set together with virtual-y | — | — |
virtual-y | Virtual anchor viewport Y coordinate (px, e.g. mouse clientY); position by coordinates when set together with virtual-x | — | — |
width | Popup width: a number (px) / "trigger" (same width as the trigger) / any CSS value; still capped by --oas-tooltip-max-width (default 240px) | string | — |
Events
| Event | Description |
|---|---|
oas-open-change | Fired when the open state changes (show/hide), detail: { open, source, reason } |
Slots
| Name | Description |
|---|---|
| default | Trigger element (hover/focus trigger); optional in virtual mode |
content | Rich content (takes precedence over the content attribute text when present) |
CSS Variables
| CSS Variable | Default |
|---|---|
--oas-glass-blur | none |
--oas-glass-ring | transparent |
--oas-tip-cross | 999px |
--oas-tooltip-animation | oas-tooltip-in |
--oas-tooltip-arrow-align-offset | 16px |
--oas-tooltip-arrow-height | var(--oas-tooltip-arrow-size, 12px) |
--oas-tooltip-arrow-radius | 0 |
--oas-tooltip-arrow-width | var(--oas-tooltip-arrow-size, 12px) |
--oas-tooltip-bg | var(--oas-color-text-primary) |
--oas-tooltip-color | var(--oas-color-bg) |
--oas-tooltip-duration | 0.15s |
--oas-tooltip-easing | ease |
--oas-tooltip-max-width | 240px |
oas-open-change: fired when the open state changes (show/hide), detail: { open, source, reason } (source is the trigger channel, reason the concrete cause; for external controlled attribute writes source is attribute). Shown/hidden on hover or focus; role="tooltip", the popup uses pointer-events: none so it never blocks interactions.