Components / overlays
Tooltip
Escape does not stopPropagation, so a dialog above still sees it. APG requires Escape to dismiss a tooltip even while the trigger keeps focus — a tooltip can cover the very content the reader is trying to see, and without Escape a keyboard user has no way to get rid of it short of leaving the control.
Summary
One sentence of explanation attached to a control.
| APG pattern | Tooltip |
| Built on | native elements |
| Status | stable |
| Since | 0.1.0 |
<Tooltip id="tip-cname" text="A DNS record that points your domain at Studio Edit.">
<Button intent="neutral" emphasis="transparent" iconOnly={<Icon path={ICONS.info} />} label="What is a CNAME?" />
</Tooltip>
Anatomy
A wrapper carrying data-se="tooltip" around the trigger, with the bubble as floater.
| Part | Element | Required |
|---|---|---|
floater | <span> | yes |
Properties
| Property | Values | Default | What it does |
|---|---|---|---|
id | string | required | Id for the bubble; the trigger's aria-describedby points at it. |
text | string | required | One sentence. If it needs more, it is not a tooltip. |
children | React.ReactElement | required | The trigger — cloned to receive aria-describedby. |
States
| State | Expressed as | |
|---|---|---|
| Hover | :hover | |
| Open or closed | data-state |
Behavior
It appears on hover and on focus, and goes on blur or Escape. It never takes focus itself.
Accessibility
The text is announced via aria-describedby, so an icon-only trigger still needs its own label: the tooltip describes, it does not name.
Never put a link or button inside one. It cannot be reached by keyboard or by touch — the tooltip is gone before the pointer arrives.
It has no upstream test suite. Tooltip is one of four patterns the W3C ships no regression tests for, so nothing external will catch a mistake here.
Keyboard
| Key | Result |
|---|---|
Escape | Dismisses it, while the trigger keeps focus |
Roles, states and properties
| Attribute | Set by |
|---|---|
role | always tooltip |
Focus
It never takes focus. It appears when the trigger is focused and goes when the trigger is not. Escape does not stopPropagation, so a dialog above still sees it — APG requires the dismissal because a tooltip can cover the very content the reader is trying to read.
Screen reader
Not recorded as verified. Stage 4 of the readiness ladder — driving each component with VoiceOver and NVDA — is unstarted across the package, and no component should claim otherwise until docs/journal/ records the session that did it.
Contrast and target size
npm run check:contrast audits every color pair this component uses across all four skins, light and dark, and fails the build on a shortfall. Two waivers are on the record in docs/audit/DEFERRED.md.
It sets no target size of its own; anything interactive inside it brings its own.
Tested
test/apg/batch-a.test.jsx
Known gaps
_No upstream requirement is recorded as not applicable._
Appearance
| Token | Used for |
|---|---|
--se-border | border |
--se-border-width | border |
--se-duration-fast | animation |
--se-ease-decelerate | animation |
--se-layer-floating | z-index |
--se-leading-body | line-height |
--se-radius-lg | border-radius |
--se-radius-md | border-radius |
--se-shadow-floating | box-shadow |
--se-space-2 | inset-block-start, padding |
--se-space-3 | padding |
--se-surface | background |
--se-text | background, border-color |
--se-text-inverse | color |
--se-text-xs | font-size |
Motion
It rises over --se-duration-fast. Leaving is not animated: a tooltip fading out follows the pointer to whatever it moved to.
Content
One sentence. If it needs two, the interface has not explained itself and the fix is a description in the page, not a longer bubble.
Never repeat the label. A tooltip on a button reading Delete that says "Delete" has cost a reader a hover for nothing.
Usage
Hover and focus only, and nothing interactive inside. That is the whole rule, and it is what separates a tooltip from a Popover — a panel that needs a click is a popover.
Not for anything the reader must have. A tooltip is invisible on touch until the reader guesses to long-press. If the information is required to use the control, put it in the page.
Notes
Hand-written, not Radix. The whole component is a wrapper, a bubble and one key handler.
Escape does not stopPropagation, so a dialog above still sees it. APG requires Escape to dismiss a tooltip even while the trigger keeps focus — a tooltip can cover the very content the reader is trying to see, and without Escape a keyboard user has no way to get rid of it short of leaving the control.