Tooltips
A tooltip explains a control that does not explain itself. It sits in the top layer as a popover, so nothing clips it, and the browser drives it wherever it can.
Types
- Plain labels the control. It shows on hover and on focus, and leaves again.
- Rich adds a subhead, text and an action, and stays until it is dismissed.
<script lang="ts">
import { Button, IconButton, RichTooltip } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<IconButton title="Add to favorites"><Icon>favorite</Icon></IconButton>
<IconButton command="show-popover" commandfor="example-rich-tooltip" aria-label="About tooltips">
<Icon>info</Icon>
</IconButton>
<RichTooltip id="example-rich-tooltip" subhead="Rich tooltip">
Room for a sentence or two, and an action.
{#snippet actions()}
<Button variant="text">Learn more</Button>
{/snippet}
</RichTooltip>
Plain tooltip
Button and IconButton take a title and do the rest: it
becomes the tooltip text and the aria-label, and a disabled or loading control drops
it. Any other element becomes the anchor by pointing aria-describedby at the
tooltip's id.
<script lang="ts">
import { Button, Tooltip } from 'noph-ui'
</script>
<Button title="This is a button tooltip">Hover over this button</Button>
<div aria-describedby="example-tooltip">Hover over this text</div>
<Tooltip id="example-tooltip">This is a basic tooltip</Tooltip>
After the pointer leaves it waits half a second before hiding, so the text inside stays reachable. Escape closes it.
Rich tooltip
A rich tooltip is persistent. A click or a tap on the control opens it, and it stays open when the pointer leaves, until the person interacts with something else: Escape or a click outside closes it. Hovering is deliberately not a trigger, so a panel with an action in it does not appear under a pointer that is only passing through.
The control points at it with commandfor, and command="show-popover" keeps a second click on the control from closing it again. A
keyboard reaches it the same way, since Enter on the control is a click.
It takes a subhead, its children as the text and an actions snippet, and
it is at most 20rem wide. bind:open, element, show and close are there to open and close it yourself, which is how Material
introduces a new feature on page load.
Positioning
A plain tooltip sits centered above its anchor, a rich one centered below its control. Neither covers it: they flip to the other side when there is no room, and shift back in when a window edge is in the way. Both sides are custom properties.
<script lang="ts">
import { Button, IconButton, RichTooltip } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<Button title="Shown below the button" --np-tooltip-position-area="bottom">Below</Button>
<Button
title="Shown to the right"
--np-tooltip-position-area="right"
--np-tooltip-justify-self="auto"
--np-tooltip-align-self="anchor-center"
>
Right
</Button>
<IconButton command="show-popover" commandfor="placement-rich-tooltip" aria-label="About placement">
<Icon>info</Icon>
</IconButton>
<RichTooltip
id="placement-rich-tooltip"
subhead="Bottom right"
--np-rich-tooltip-position-area="bottom right"
--np-rich-tooltip-justify-self="start"
>
Room for a sentence or two.
</RichTooltip>
That last one is the placement Material specifies by default: the panel's top left corner sits at the control's bottom right corner. Centered below, the way this component leaves it, is the variant Material allows on desktop.
A vertical toolbar does this for you. Its actions are stacked, so a tooltip above one of them would cover the one before it, and the toolbar moves them to the trailing side instead.
Without JavaScript
Everything a tooltip needs is in the markup the server sends: interestfor on a plain
tooltip's anchor, command and commandfor on a rich tooltip's control,
the popover itself and CSS for the timing. Either attribute also makes the control the implicit
anchor of its popover, so there is no anchor-name to set.
<button interestfor="save-tip" aria-describedby="save-tip">
Save
</button>
<Tooltip id="save-tip">Save the file</Tooltip>interestfor only exists on a, area and button. Any other anchor, and any browser without it, falls back to listeners the
plain tooltip attaches on mount. A rich tooltip attaches none at all: opening it is a click on its
control, which the browser handles on its own.
Touch devices
A link or a button shows its plain tooltip on long press, and a rich tooltip opens on tap. The
fallback has no hover to work with, so it stays quiet on (hover: none) and (pointer: coarse). Never put information in a tooltip that is not
available anywhere else.
Accessibility
Both render role="tooltip". A plain tooltip hangs off its anchor's aria-describedby; a rich one gets aria-expanded and aria-details from commandfor, and Tab moves from the control into
the panel.
A plain tooltip opens on keyboard focus but not on a plain click, so it stays out of the way of a
pointer user; a rich one opens on Enter, the same click a pointer makes. On an
icon-only control, title gives the button its name and its description in one go. An action
inside a tooltip is a shortcut, never the only way to get somewhere.
<IconButton title="Delete message">
<Icon>delete</Icon>
</IconButton>Theming
| Token | Default value |
|---|---|
--np-tooltip-position-area | top |
--np-tooltip-justify-self | anchor-center |
--np-tooltip-align-self | auto |
--np-tooltip-margin | 4px 0 |
--np-tooltip-position-try-fallbacks | flip-block |
--np-rich-tooltip-position-area | bottom |
--np-rich-tooltip-justify-self | anchor-center |
--np-rich-tooltip-align-self | auto |
--np-rich-tooltip-margin | 4px 0 |
--np-rich-tooltip-position-try-fallbacks | flip-block |
--np-rich-tooltip-action-inset | 1rem |
Both position properties take any CSS position-area value, for example right or bottom right. The colors come from the theme: a plain tooltip
uses the inverse-surface roles, a rich one surface-container and on-surface-variant.
The action row is pulled out by --np-rich-tooltip-action-inset so the label of the
action lines up with the text above it. It matches the inline padding of a small Button, so set it to the padding of the action you use if that differs.
API
Tooltip attributes
Everything else is forwarded to the tooltip element. role is set by the component.
| Attribute | Type | Default | Description |
|---|---|---|---|
id | string | undefined | undefined | Connects the tooltip to its anchor, the element with a matching aria-describedby. |
open | boolean | undefined | undefined | Bindable. Reflects whether the tooltip is shown. |
element | HTMLDivElement | undefined | undefined | Bindable reference to the tooltip element. |
Rich tooltip attributes
Everything else is forwarded to the panel. role and popover are set by the
component.
| Attribute | Type | Default | Description |
|---|---|---|---|
id | string | undefined | undefined | Connects the tooltip to its control, the element with a matching commandfor. |
subhead | string | undefined | undefined | Title line above the text. |
actions | Snippet | undefined | undefined | Row below the text, meant for one or two text buttons. |
open | boolean | undefined | undefined | Bindable in both directions. Setting it opens or closes the tooltip. |
element | HTMLDivElement | undefined | undefined | Bindable reference to the panel. |