Developer Experience (DX)
Next, let's look at how developers use the Tooltip. You give it its text and wrap it around the trigger, which takes a few props from it, and it handles when to open, where and for whom.
import { Button } from "@/components/Button";
import { Tooltip } from "@/components/Tooltip";
export function SettingsButton() {
return (
<Tooltip label="Settings" type="label">
{(trigger) => <Button {...trigger} label="Settings" variant="ghost" iconOnly leading={<SettingsIcon />} />}
</Tooltip>
);
}
Consistent
The Tooltip uses the same names as every other component in Open Components, so there's little new to learn:
| Name | Kind | What it means |
|---|---|---|
label | Prop | The text content, when it's plain text, as on the Button |
type | Prop | Whether its text is the trigger's label or a description |
delay | Prop | How long the pointer rests on the trigger before it opens, in ms |
placement | Prop | The side of the trigger it opens on: top, bottom, start or end |
arrow | Prop | Draws an arrow that points at the trigger |
We recommend type="description", delay={500}, placement="top" and an arrow as the defaults. The Tooltip also leaves a few things out, on purpose:
- There's no slot for its content, only
label. A slot would invite links, buttons and icons, which nobody could reach (see Keep it short and plain). - There's no
openprop. When a tooltip opens is what makes it accessible, so the Tooltip keeps it to itself, and something you'd want to show on your own terms is a Popover. - There's no
colororsize. A tooltip has no intent of its own, and its text is always small and short. - There's no
disabledeither. To have no tooltip, render the trigger without one.
Type-safe
Props and the trigger's bindings are typed, so your editor can autocomplete them and your type checker (tsc, vue-tsc, svelte-check, ngc or astro check) catches mistakes like placement="left", which isn't one of the Tooltip's placements, or a tooltip without a label.
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | The text it shows. Required, and plain text only | |
type | "label" | "description" | "description" | Whether it shows the trigger's own name, or describes it |
delay | number | 500 | How long the pointer rests on the trigger before it opens, in milliseconds |
placement | "top" | "bottom" | "start" | "end" | "top" | The side of the trigger it opens on, before it flips |
arrow | boolean | true | Draws an arrow that points at the trigger, on whichever side it opens |
| Slot | Description |
|---|---|
default | The trigger, which binds trigger on its focusable element |
The trigger's bindings are typed as TooltipTrigger: aria-describedby for a description, the anchor's name in style, and the pointer and focus listeners. Bind them all, on the element that takes focus. The Tooltip fires no events of its own. We also export TooltipProps, which comes in handy for wrappers:
import { Tooltip, type TooltipProps } from "@/components/Tooltip";
export function Hint(props: TooltipProps) {
return <Tooltip delay={300} {...props} />;
}
Composable
The Tooltip adds no wrapper around its trigger. It renders the trigger you give it, then the tooltip right after it, so your layout, your flex gaps and your trigger's own attributes stay as they are. How the trigger takes its props is the one thing that differs from framework to framework:
| Framework | The trigger |
|---|---|
| React | A function child, which gets trigger to spread on the element |
| Vue | The default slot, with trigger to bind with v-bind |
| Svelte | A children snippet, which gets trigger to spread |
| Angular | The element the appTooltip directive is on, which adds the tooltip after it, with tooltipType, tooltipDelay, tooltipPlacement, tooltipArrow and tooltipClass as its other inputs |
| Solid | A function child, like React's |
| Astro | The default slot, which a <script> wires up in the browser, as the Button's Astro examples do |
| Vanilla | The element right before the tooltip, which a script wires up |
Bind the props on the element that takes focus, which is usually the trigger's root. Components like the Button pass them straight through, attributes and listeners alike, so binding them on the Button is enough. If your trigger's root isn't the focusable element, like a card with a button inside it, bind them on the button instead.
With other components
- An icon-only Button keeps its visually hidden label, and its tooltip shows that same label, with
type="label". - A Button with
focusableWhenDisabledstays focusable while it's disabled, so its tooltip can still say why (see Disabled triggers). - An Input is described by its hint, which stays on the page rather than in a tooltip, since people need it while they type.
- A Dialog or Popover around a Tooltip keeps its own
Escapehandling. The Tooltip only takesEscapewhile it's open, and stops it there. - A toggletip, which shows a hint when a button is pressed, and anything with links or buttons inside, will be a Popover, on our Roadmap.
Controllable
The Tooltip keeps its open state to itself, since when it opens is what makes it work for every input. Everything else is within reach:
| What | How you control it |
|---|---|
| Text | label |
| Type | type |
| Delay | delay, for the pointer. Focus always opens it at once |
| Placement | placement, before it flips |
| Arrow | arrow, on by default, and its size with --tooltip--arrow--size |
| Shown | Render the Tooltip around the trigger, or render the trigger alone |
| Look | Its variables, from a class |
| Open | Read data-state, which is open or closed |
DX Checklist
| Rule | Level | Scope | Requirement | Basis | Check |
|---|---|---|---|---|---|
tooltip/trigger-props | Must | Component | The trigger is the element you render, which binds the props the Tooltip hands it, with no wrapper element | Open Components | Unit test |
tooltip/label-prop | Should | Component | Its text comes from label, the name every component uses for plain text | Open Components | Type check |
tooltip/attributes-on-tooltip | Should | Component | Classes and attributes you pass land on the tooltip, so a class can set its variables | Open Components | Unit test |