GrTooltip
Shows a short hint on hover or focus.
Overview
When to take it
- a label for an icon button — the only way to give a name to a control that has no visible text;
- the expansion of an abbreviation or a status — a short text that would otherwise take up room in the layout;
- the hint appears on focus as well — not only on hover, so it is reachable from the keyboard;
- there is little text — one or two lines: a long text in a hint can neither be selected nor scrolled.
When to take something else
| Need | Take |
|---|---|
| Interaction is needed inside: a link, a button, a field | GrPopover |
| A list of actions | GrDropdownMenu |
| A message the user has to notice | GrAlert |
| A message about the result of an action | GrToaster |
| A permanent explanation under a field | GrFormField |
A hint cannot be the only carrier of information. On a touch device there is no hovering, and a text available only through it does not exist at all for some users. What the task cannot be completed without lives in the layout itself — as a label, as the hint of a field or as a line beside it.
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrTooltip open text="Saves without closing the form"><GrButton>Save draft</GrButton></GrTooltip>Examples 3 each with a live demo and its code
Icon buttons that name themselves
The default slot takes the trigger, and the button inside it is the only Tab stop. Its name is its own
aria-labeland its shortcutaria-keyshortcuts, so the tooltip — the same words with the keys for the eyes, through thecontentslot — is not read twice:describe-triggeris off.open-delaykeeps the tooltips from flashing while the pointer runs along the row.Note on the invoice Thank you for your business. Payment within 30 days to IE29 AIBK 9311 5212 3456 78.
Terms in a plan comparison
Without a trigger of its own the tooltip draws an info icon and becomes a Tab stop;
textis the whole explanation.placement="top-start"lifts it above the row so the prices it explains stay in sight,size="sm"matches the table andicon-colorthe muted text around it. The numbers stay in the table — a tooltip cannot be the only carrier: on a touch screen there is no hover.Plan Team · €49 Business · €149 Seats 10 50 Overage €0.20 each €0.10 each SSO — Included Long names in a narrow column
Each row is the trigger, so
blockstretches the wrapper to the row. Only a name cut by the ellipsis gets a tooltip — the others aredisabled, because repeating the visible text is noise. «Where is the export?» opens the export button’s tooltip for a few seconds throughv-model:open, withoffset-pxkeeping it clear of the button.Customers- Northwind Traders
- Fabrikam Industrielle Fertigung und Vertrieb GmbH & Co. KG
- Contoso Ltd
- Adventure Works Cycles Manufacturing and Distribution, S.L.
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
placement | Placement | undefined | "top" | The preferred side of the panel; `flip` may change it. |
disabled | boolean | undefined | false | The tooltip is not shown by anything — neither by the cursor, nor by the focus, nor by `v-model:open`. |
openv-model | boolean | undefined | undefined | Controlled visibility. Without it the component runs the visibility itself. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | The size of the panel and of the default trigger icon. |
block | boolean | undefined | false | The wrapper takes the width of the parent instead of the width of the content. Needed where the trigger is obliged to stretch: a list item, a menu row, a cell. The default `inline-flex` would collapse such a trigger to its content, and the highlight and the focus ring would shrink along with it. |
offsetPx | number | undefined | undefined | The gap between the trigger and the panel, in px. |
openDelay | number | undefined | 0 | The delay before showing, in ms. It saves from flickering while running the cursor along a panel of buttons. |
closeDelay | number | undefined | 0 | The delay before hiding, in ms. |
text | string | undefined | undefined | The text of the tooltip. Markup instead of text is the `#content` slot. |
iconColor | string | undefined | "var(--gr-muted-fg)" | The colour of the trigger icon (a CSS colour). By default — `var(--gr-muted-fg)`. |
describeTrigger | boolean | undefined | true | Whether to hang `aria-describedby` on the trigger. Yes by default. `false` — when the tip **repeats verbatim** the accessible name of the trigger: then it is not a description but noise, and the screen reader pronounces the label twice. That is how a collapsed `GrSidebarItem` is built: its name is already in `aria-label`. |
Slots
| Slot | Type | Description |
|---|---|---|
default | any | The trigger. By default — an info icon. |
content | any | The content of the tooltip; it replaces the `text` prop. |
Events
| Event | Signature | Description |
|---|---|---|
update:open | (event: "update:open", value: boolean): void | — |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrTooltipPlacement, GrTooltipProps, GrTooltipEmits } from '@feugene/granularity/components/GrTooltip'Filedist/types/components/GrTooltip/GrTooltip.vue.d.ts
type GrTooltipPlacement = UseFloatingPlacement;interface GrTooltipProps {
text?: string;
iconColor?: string;
size?: GrTooltipSize;
placement?: GrTooltipPlacement;
offsetPx?: number;
openDelay?: number;
closeDelay?: number;
disabled?: boolean;
open?: boolean;
block?: boolean;
describeTrigger?: boolean;
}interface GrTooltipEmits {
(e: 'update:open', value: boolean): void;
}Filedist/types/components/GrTooltip/config.d.ts
const grTooltipConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrTooltip">;Filedist/types/components/GrTooltip/defaults.d.ts
interface GrTooltipConfigurableProps {
size: GrComponentSize;
}Filedist/types/components/GrTooltip/grTooltipStyles.d.ts
type GrTooltipSize = GrComponentSize;Filedist/types/components/GrTooltip/safelist.d.ts
const grTooltipSafelist: string[];Accessibility
Keys
Esc
Full contract of the package: docs/keyboard.md ↗
Usage from the library prose
The trigger and the tab order
Without a slot the trigger is an info icon, and the wrapper becomes the Tab stop. With a slot the
wrapper steps aside: if there is a focusable element inside (a button, a link, a field),
aria-describedby travels onto it, and the wrapper loses its tabindex. Otherwise one visual
control would take two tab stops, and the outer one a role-less <span>.
If there is nothing to focus in the slot (text, a picture), the wrapper stays a Tab stop —
otherwise the hint would not be available from the keyboard at all. But only while the hint has
something to show: with disabled (or without text) the wrapper drops both tabindex and
aria-describedby. The typical case is a hint with the full text switched on only for a
truncated list row: the other rows add no extra stops.
describe-trigger="false" cancels aria-describedby. Needed where the hint repeats the
accessible name of the trigger word for word: a description with the same text is read by
the narrator a second time round, and the result is “Billing, Billing”. That is how a
collapsed GrSidebarItem is wired up — its name comes from aria-label, and the hint is for
the eyes.
<!-- The wrapper is transparent: there is one Tab stop — the button itself. -->
<GrTooltip text="Delete irreversibly">
<GrButton variant="ghost" square><IconTrash /></GrButton>
</GrTooltip>The content
text is a short string; markup instead of it is the #content slot. Without either the hint does
not open: an empty panel is worse than a missing one.
The width of the trigger
Both wrappers — the outer one and the trigger one — are inline-flex by default, that is,
the hint shrinks to its content. block turns them into flex w-full: needed when the
trigger is obliged to take the width of the parent — a list item, a menu row, a cell. The
prop changes both wrappers: a class from outside does not reach the inner one, and the
outer one alone is not enough — the trigger would still collapse together with its highlight
and its focus ring. The content of such a trigger is aligned to the start, like a list row,
rather than to the centre.
The placement
placement is any side from the @floating-ui scale (top, right-start, …), and offsetPx is
the gap to the trigger. The resulting side may differ from the one that was set: flip turns the
hint over when there is not enough room, and shift keeps it from coming off the edge of the
screen.
The delays
openDelay and closeDelay (in ms, both 0 by default). A delay before showing is needed where the
hints stand densely — on a bar of buttons the hint would otherwise blink on every pass of the
cursor. Escape and a click outside close it instantly, bypassing closeDelay: a delay there would
read as sticking.
Control from the outside
disabled forbids showing entirely. v-model:open puts the visibility under the control of the
owner — the component stops opening itself and only reports the intent through update:open.
Touch
On touch devices hover does not exist, and focus on a tap is not guaranteed, so a tap on the trigger toggles the hint, and a tap outside closes it.
Props in this example