Skip to the content

Kongens Nytorv

Metro M1, M2 and M3.

Props

NameTypeDefaultDescription
childrenReactNode—The tooltip's words: a short label in sentence case that matches the trigger's accessible name, such as "Zoom in". It takes the popup's inverse text colour.
trigger*ReactElement<unknown, JSXElementConstructor<any> | string>—Required. The control the tooltip labels, such as an IconButton, rendered in place of the tooltip. It must take a ref and spread the props it is given, as Perron's buttons do, and carry its own accessible name.
openboolean—Whether the tooltip is shown. Set it to control the tooltip, with onOpen and onClose to hear when it asks to change; leave it out for a tooltip its trigger opens.
defaultOpenbooleanfalseWhether the tooltip is shown at first, when open is left out.
onOpen(event: Event, details: Tooltip.Root.ChangeEventDetails) => void—Called when the tooltip asks to open: reason in details is 'trigger-hover' when the pointer rests on the trigger or 'trigger-focus' when the keyboard focuses it, and details.cancel() keeps it closed.
onClose(event: Event, reason: Tooltip.Root.ChangeEventReason, details: Tooltip.Root.ChangeEventDetails) => void—Called when the tooltip asks to close: reason is 'trigger-hover', 'trigger-focus', 'trigger-press', 'escape-key', 'outside-press' or another of Base UI's reasons, and details.cancel() keeps it open.
refRef<HTMLDivElement>—Reaches the root, the popup.
slotProps{ trigger?: Props<unknown> | undefined; portal?: TooltipPortalProps | undefined; positioner?: TooltipPositionerProps | undefined; }—Props for each part: trigger, portal, positioner.
childrenReactNode, default —
The tooltip's words: a short label in sentence case that matches the trigger's accessible name, such as "Zoom in". It takes the popup's inverse text colour.
trigger (required)ReactElement<unknown, JSXElementConstructor<any> | string>, default —
Required. The control the tooltip labels, such as an IconButton, rendered in place of the tooltip. It must take a ref and spread the props it is given, as Perron's buttons do, and carry its own accessible name.
openboolean, default —
Whether the tooltip is shown. Set it to control the tooltip, with onOpen and onClose to hear when it asks to change; leave it out for a tooltip its trigger opens.
defaultOpenboolean, default false
Whether the tooltip is shown at first, when open is left out.
onOpen(event: Event, details: Tooltip.Root.ChangeEventDetails) => void, default —
Called when the tooltip asks to open: reason in details is 'trigger-hover' when the pointer rests on the trigger or 'trigger-focus' when the keyboard focuses it, and details.cancel() keeps it closed.
onClose(event: Event, reason: Tooltip.Root.ChangeEventReason, details: Tooltip.Root.ChangeEventDetails) => void, default —
Called when the tooltip asks to close: reason is 'trigger-hover', 'trigger-focus', 'trigger-press', 'escape-key', 'outside-press' or another of Base UI's reasons, and details.cancel() keeps it open.
refRef<HTMLDivElement>, default —
Reaches the root, the popup.
slotProps{ trigger?: Props<unknown> | undefined; portal?: TooltipPortalProps | undefined; positioner?: TooltipPositionerProps | undefined; }, default —
Props for each part: trigger, portal, positioner.

Use it

import { Tooltip } from '@mylder/perron'

Ask your agent

get_reference({ library: "base-ui", component: "Tooltip" })

Keyboard and ARIA

From the Base UI reference for Tooltip and the installed TooltipRoot, TooltipTrigger, TooltipPopup and the focus, hover and dismiss hooks they build on. "From" says who does it.

KeyDoesFrom
Tab, Shift+TabMoving focus onto the trigger opens its tooltip at once, calling onOpen with 'trigger-focus'; moving it off closes the tooltip, calling onClose with 'trigger-focus'The browser moves focus; Base UI opens
EscapeWhile the tooltip is open, from anywhere in the document: calls onClose(event, 'escape-key', details), and focus stays on the triggerBase UI
Enter, Space on triggerRun the trigger's own action, and close the tooltip with 'trigger-press' while closeOnClick is onThe browser, the trigger; Base UI

No other key: the popup is out of the tab order (tabindex="-1") and holds no control, so Tab passes it by.

Tooltip adds no ARIA of its own, neither role="tooltip" on the popup nor aria-describedby or aria-label on the trigger, leaving the trigger named by its own words, as Base UI's guidelines ask. Perron's triggers already carry their names (an IconButton's required label, a Button's text), so a description that repeats the name would be read twice; aria-describedby would point at a popup that exists only while open, so the description would come and go with the hover; and the contract's first checklist line holds because the tooltip's words match the trigger's name, which the Default story checks. A word that says more than the name belongs in visible text or a popover, as the Do and don't table says.

ElementRoleAccessible name fromStates announced
triggerThe caller's element's own, such as buttonIts own content or aria-label, such as IconButton's labelIts own, such as disabled; none added
positionerpresentation, from Base UInonenone
rootnone: a <div> that reads as its textnone: its children are read as textnone

What axe won't catch, from the contract, each with how this component meets it:

  • Everything the eye is told, the ear is told, for every shape each prop accepts: the tooltip's words match the trigger's accessible name, which the trigger carries whether the tooltip is open or not; the popup draws no mark.
  • Every landmark and named region has an accessible name: none here; the popup is not a landmark or a region.
  • Every default label can be overridden: none here; Tooltip owns no strings.
  • Every shape the type accepts renders, in every variant: children renders a string, an element and a list alike, as the Content story shows.
  • A hidden copy kept for animation is aria-hidden, and a collapsed region is inert: none here; the popup leaves the document when it closes, and Base UI makes the positioner inert while it is closed.
  • Native semantics first: the trigger is the caller's native <button> or Base UI button, and the popup a plain <div> for words, with no role.
  • Focus moves in reading order and returns on close: the popup never takes focus, so opening and closing it leaves focus on the trigger.
  • Every target is at least size.target-min square: the trigger, the caller's control, such as IconButton's size.control square; the popup takes no press.
  • Loops stop when the system asks for less motion: none here; the popup has no loop and no transition.

Owned strings

No owned strings: every word in the popup comes from the caller, as children, and the trigger's name from the trigger.

Do and don't

For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.

RuleLevelDoDon'tChecked by
DD-1mustGive an icon-only control a tooltip with the same words as its accessible name: an IconButton labelled "Zoom in" and the tooltip "Zoom in"Write tooltip words that differ from the trigger's name: a screen reader hears only the namereview
DD-2mustKeep the words to a short label, in sentence casePut a sentence the reader needs in a tooltip: touch and screen reader users never see it, so put it in visible text or a popoverreview
DD-3mustPass a control that takes a ref and spreads its props as trigger, such as a Button, an IconButton or a ToolbarButtonPass a component that drops the props it is given: Base UI's hover and focus handlers never reach the element, and the tooltip never opensreview
DD-4mustKeep a disabled trigger focusable with focusableWhenDisabled, so its tooltip still opens from the keysPut the tooltip on a natively disabled button: it leaves the tab order, so the keyboard can't reach the tooltipreview
DD-5mustPut a button in a toolbar in a tooltip with trigger={<ToolbarButton … />}, the way round Toolbar givesPass the tooltip's trigger to the button's render, as for popupsreview
DD-6shouldKeep the app inside one PerronProvider, so a row of tooltips opens at once after the firstAdd a provider per tooltip: each waits the full delay againreview
DD-7mustUse MapTooltip for a card against a point on the mapWrap the map canvas in a Tooltip: it has one trigger, the whole canvas, not the vehicle under the pointerreview
DD-8mustPut only words in the tooltipPut a link, a button or a field in it: the popup is out of the tab order, so the keys can't reach themreview
DD-9shouldChange the side through slotProps.positioner, such as side: 'bottom'Move the popup with a margin or a transform: Base UI's placement and flipping no longer match what is drawnreview