A popover that opens on hover after a delay and, always, on keyboard focus. A plain tooltip labels its trigger; the richer form can be moved into and reached by keyboard.
A tooltip opens on hover and on keyboard focus, as Radix’s does, and a tap does not reliably open one: iOS Safari does not focus a button when it is tapped. Put nothing in a tooltip that a reader on a phone needs. A control’s name belongs in aria-label or its text; a longer explanation belongs in a hui-popover, which opens on a tap, as here.
On touch, a popover instead375 px wideSource
<hui-popover>
<hui-buttonslot="trigger"variant="outline">What is a workspace?</hui-button>
<pstyle="margin:0;font-size:.875rem">A workspace holds your projects and the people who can see them.</p>
</hui-popover>
API
Attributes2
Attributes
Name
Type
Default
Description
open
boolean
false
Reflected open state, inherited from the popover base.
placement
"top" | "bottom" | "left" | "right"
"top"
Inherited from the popover base, but forced to top when the tooltip connects.
Properties4
Properties
Name
Type
Default
Description
open
boolean
false
Reflects to the open attribute.
placement
OverlayPlacement
'top'
Reflects to the placement attribute; reset to top on connect.
rich
boolean
false
A plain class field, read once in connectedCallback(). It is not a reactive property and has no attribute binding, so set it on the element before it connects.
delay
number
400
Milliseconds before hover opens the tooltip. Set from rich on connect, so setting it after connection has no effect.
Methods2
Methods
Name
Type
Description
show(from?)
-
Opens the tooltip and records from as the element focus returns to. Hover and focus call this with the trigger.
hide()
-
Closes the tooltip; focus is not moved.
Events3
Events
Name
Description
hui-open
The tooltip entered the top layer. It bubbles and is composed, and carries no detail.
hui-close
The tooltip left the top layer. It bubbles and is composed, and carries no detail.
pointerenter / focusin on [slot="trigger"]
Hover opens it after delay; keyboard focus opens it immediately, cancelling any pending hover timer.
Slots2
Slots
Name
Description
trigger
The element the tooltip describes.
(default)
The tooltip content; for a rich tooltip it may contain interactive elements.
CSS custom properties4
CSS custom properties
Name
Default
Description
--hui-popover
oklch(1 0 0)
Panel background.
--hui-popover-foreground
oklch(0.145 0.008 326)
Panel text colour.
--hui-border
oklch(0.922 0.005 325.62)
Panel border.
--hui-radius-md
calc(var(--hui-radius, 0.45rem) * 0.8)
Panel corner radius.
::part() hooks2
::part() hooks
Name
Description
anchor
The span carrying the CSS anchor-name the panel points at.
panel
The native popover panel, carrying role="tooltip".
States
Rich tooltip
rich is the interactive form: a longer delay, and a grace period that lets the pointer travel into the panel, so the link inside can be reached.
<p>More detail with a <aclass="hui-link"href="#more">link</a>.</p>
</div>
</hui-tooltip>
Server-side mechanics
The server owns the trigger and the default-slot content; it is not form-associated and submits nothing. It inherits the popover's reflected open and placement, but the normal path is interaction: it opens on hover after delay and immediately on keyboard focus, and because focus restoration is switched off it never moves focus on close. On open it writes an aria-describedby from the trigger to the content id, generating the id when absent, so a swap that replaces the trigger removes that link with it. An hx-swap can replace the trigger or content while the panel is up; swapping the tooltip element itself closes it through disconnectedCallback(), leaving nothing in the top layer.
Accessibility
The panel carries role="tooltip" and the trigger gets an aria-describedby pointing at the content id, generated if absent.
It opens on hover after a delay and always on keyboard focus, because a tooltip a keyboard user cannot see is not a tooltip.
Escape dismisses it without moving focus; the popover base's focus restoration is switched off for exactly this reason.
A rich tooltip has a longer delay and a close grace period so the pointer can travel into its content, which is reachable by keyboard.
A tooltip must never be the only route to essential information; that is a documentation rule the host applies.
On touch it cannot be relied on to open at all: a tap is neither a hover nor, in iOS Safari, a focus. Content a phone reader needs belongs in the control’s text, its aria-label, or a hui-popover, which opens on a tap. See “On a phone” above.
Keyboard
Keyboard
Keys
Action
Tab
Moving keyboard focus onto the trigger opens the tooltip immediately.
Escape
Closes the tooltip without moving focus, as WCAG 1.4.13 requires.
Tab (rich)
The panel follows the anchor in the DOM, so Tab from the trigger reaches interactive content in a rich tooltip without a trap.
Gotchas
rich is a plain class field, not a reactive property, so the rich attribute does not set it: <hui-tooltip rich> behaves as a plain tooltip, with a 400 ms hover delay, no travel grace period, and an aria-describedby still written to the trigger. Set it as a property before the element connects, or the attribute is silently ignored.
delay is a plain class field too, set from rich in connectedCallback(); assigning it after connection has no effect.
placement is forced to top in connectedCallback(), so a host-supplied placement on the element is overwritten when it connects.