Tooltip
A short explanation, on hover and on focus.
No JavaScript, deliberately
A tooltip is the component most often built as a portal with a positioning library. The version that costs nothing does the same job here: CSS shows it on :hover and on :focus-within, so it works before hydration, inside a server component, and with a keyboard. Tab to the button below without touching the mouse.
Why not title=""
The native attribute waits about a second, cannot be styled, is announced inconsistently across screen readers, and never appears for a keyboard user at all. The one thing it still does better is survive on touch, where nothing hovers — which is the real limit here.
Nesting the bubble inside a button would fold its text into the button's accessible name, and the control ends up announced as its own explanation: “Retry, retries the run from the failed step, keeping its inputs, button”.
In both themes
light
dark
Guidance
Use it when
- A hint that is nice to have: what an abbreviation stands for, what a status means, what a button will do.
- On an icon-only control, alongside — never instead of — its aria-label.
- Short. One line. This is a hint, not documentation.
Reach for something else when
- Anything that must be read. Nothing hovers on a phone, so a tooltip on touch is content nobody sees.
- Interactive content. There is no way to move the pointer into this bubble without leaving the trigger.
- A form field's requirements. That is Field's hint, which is wired with aria-describedby and always visible.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| content | ReactNode | — | The hint. One line. |
| side | "top" | "bottom" | "top" | Which way it opens. Two options on purpose — anything cleverer needs measuring, and measuring needs JavaScript. |
| children | ReactNode | — | The trigger. Must be focusable for the keyboard half to work. |
Accessibility
The bubble is role="tooltip" and the trigger points at it with aria-describedby, so it is announced as a description rather than as part of the name. It is hidden with opacity rather than display: none — a display-toggled tooltip is skipped by some screen readers even when aria-describedby points straight at it. And :focus-within is what makes tabbing to the trigger enough: a hover-only tooltip is invisible to anyone navigating by keyboard, which in a console is a large share of the people using it.