GrTooltip

  • core
  • navigation
  • API · 11 props
  • ⌨ keys

Shows a short hint on hover or focus.

import { GrTooltip } from '@feugene/granularity/components/GrTooltip'

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

NeedTake
Interaction is needed inside: a link, a button, a fieldGrPopover
A list of actionsGrDropdownMenu
A message the user has to noticeGrAlert
A message about the result of an actionGrToaster
A permanent explanation under a fieldGrFormField

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

Stage

Loading…

0 changed· state is in the URL

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-label and its shortcut aria-keyshortcuts, so the tooltip — the same words with the keys for the eyes, through the content slot — is not read twice: describe-trigger is off. open-delay keeps 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; text is the whole explanation. placement="top-start" lifts it above the row so the prices it explains stay in sight, size="sm" matches the table and icon-color the 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.

    PlanTeam · €49Business · €149
    Seats 1050
    Overage €0.20 each€0.10 each
    SSO —Included
  • Long names in a narrow column

    Each row is the trigger, so block stretches the wrapper to the row. Only a name cut by the ellipsis gets a tooltip — the others are disabled, because repeating the visible text is noise. «Where is the export?» opens the export button’s tooltip for a few seconds through v-model:open, with offset-px keeping 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

PropTypedefaultDescription
placementPlacement | undefined"top"The preferred side of the panel; `flip` may change it.
disabledboolean | undefinedfalseThe tooltip is not shown by anything — neither by the cursor, nor by the focus, nor by `v-model:open`.
openv-modelboolean | undefinedundefinedControlled visibility. Without it the component runs the visibility itself.
size"xs" | "sm" | "md" | "lg" | undefinedundefinedThe size of the panel and of the default trigger icon.
blockboolean | undefinedfalseThe 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.
offsetPxnumber | undefinedundefinedThe gap between the trigger and the panel, in px.
openDelaynumber | undefined0The delay before showing, in ms. It saves from flickering while running the cursor along a panel of buttons.
closeDelaynumber | undefined0The delay before hiding, in ms.
textstring | undefinedundefinedThe text of the tooltip. Markup instead of text is the `#content` slot.
iconColorstring | undefined"var(--gr-muted-fg)"The colour of the trigger icon (a CSS colour). By default — `var(--gr-muted-fg)`.
describeTriggerboolean | undefinedtrueWhether 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

SlotTypeDescription
defaultanyThe trigger. By default — an info icon.
contentanyThe content of the tooltip; it replaces the `text` prop.

Events

EventSignatureDescription
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
import type { GrTooltipPlacement, GrTooltipProps, GrTooltipEmits } from '@feugene/granularity/components/GrTooltip'

Filedist/types/components/GrTooltip/GrTooltip.vue.d.ts

GrTooltipPlacementtype
type GrTooltipPlacement = UseFloatingPlacement;
GrTooltipPropsinterface
interface GrTooltipProps {
    text?: string;
    iconColor?: string;
    size?: GrTooltipSize;
    placement?: GrTooltipPlacement;
    offsetPx?: number;
    openDelay?: number;
    closeDelay?: number;
    disabled?: boolean;
    open?: boolean;
    block?: boolean;
    describeTrigger?: boolean;
}
GrTooltipEmitsinterface
interface GrTooltipEmits {
    (e: 'update:open', value: boolean): void;
}

Filedist/types/components/GrTooltip/config.d.ts

grTooltipConfigconst
const grTooltipConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrTooltip">;

Filedist/types/components/GrTooltip/defaults.d.ts

GrTooltipConfigurablePropsinterface
interface GrTooltipConfigurableProps {
    size: GrComponentSize;
}

Filedist/types/components/GrTooltip/grTooltipStyles.d.ts

GrTooltipSizetype
type GrTooltipSize = GrComponentSize;

Filedist/types/components/GrTooltip/safelist.d.ts

grTooltipSafelistconst
const grTooltipSafelist: string[];

Accessibility

Keys

Esc

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.

vue
<!-- 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.