Tooltip
A popup that displays information related to an element when it receives keyboard focus or the mouse hovers over it.
- v1.0.0
- WAI-ARIA: Tooltip
- SSR
- Zoneless
- 3.1 kB gzip
- +11.5 kB Angular CDK, shared
import { Component } from '@angular/core';
import { Button } from '@/ui/button/button';
import {
Tooltip,
TooltipPortal,
TooltipTrigger,
TooltipContent,
} from '@/ui/tooltip/tooltip';
/** A tooltip describing a button, shown on hover and keyboard focus. */
@Component({
selector: 'example-tooltip-demo',
imports: [Tooltip, TooltipTrigger, TooltipPortal, TooltipContent, Button],
template: `
<ng-container ndTooltip>
<button ndButton ndTooltipTrigger variant="outline">Hover</button>
<ng-template ndTooltipPortal>
<div ndTooltipContent>Add to library</div>
</ng-template>
</ng-container>
`,
})
export class TooltipDemoExample {}
A tooltip shows a short text label for an element when it is hovered or
focused, after a short delay. It describes the trigger through
aria-describedby, stays open while the pointer is over it, and closes on
Escape, as WCAG 1.4.13 requires. It never takes focus, so it can't hold
links or buttons.
Installation
npx @ndop/cli add tooltippnpm dlx @ndop/cli add tooltipyarn dlx @ndop/cli add tooltipbunx @ndop/cli add tooltipInstall the dependencies
npm install @ndop/primitives @ndop/corepnpm add @ndop/primitives @ndop/coreyarn add @ndop/primitives @ndop/corebun add @ndop/primitives @ndop/coreCopy the code into your project
At the paths your
ndop.jsonaliases point to; these use the defaults.src/app/ui/tooltip/tooltip.tsimport { computed, Directive, input } from '@angular/core'; import { NdTooltip, NdTooltipContent, NdTooltipPortal, NdTooltipTrigger, } from '@ndop/primitives/tooltip'; import { cn } from '@/lib/utils'; /** * A tooltip (`@ndop/primitives/tooltip`): a short description of its * trigger, shown after a delay on hover and at once on keyboard focus. Put * it on an `ng-container`. * * @example * ```html * <ng-container ndTooltip>…</ng-container> * ``` */ @Directive({ selector: '[ndTooltip]', hostDirectives: [ { directive: NdTooltip, inputs: ['open', 'defaultOpen', 'openDelay', 'closeDelay'], outputs: ['openChange'], }, ], }) export class Tooltip {} /** * The element its tooltip describes, which shows it on hover and focus. * * @example * ```html * <button ndButton ndTooltipTrigger>Add to library</button> * ``` */ @Directive({ selector: '[ndTooltipTrigger]', hostDirectives: [NdTooltipTrigger], host: { 'data-slot': 'tooltip-trigger' }, }) export class TooltipTrigger {} /** * Marks the `ng-template` its tooltip renders while open. * * @example * ```html * <ng-template ndTooltipPortal>…</ng-template> * ``` */ @Directive({ selector: 'ng-template[ndTooltipPortal]', hostDirectives: [NdTooltipPortal], }) export class TooltipPortal {} /** * The tooltip bubble. * * @example * ```html * <div ndTooltipContent>Delete permanently</div> * ``` */ @Directive({ selector: '[ndTooltipContent]', hostDirectives: [ { directive: NdTooltipContent, inputs: ['side', 'align', 'sideOffset'] }, ], host: { 'data-slot': 'tooltip-content', '[class]': 'classes()' }, }) export class TooltipContent { /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn( 'z-50 w-fit animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=start]:slide-in-from-end-2 data-[side=end]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95', this.class(), ), ); }src/app/lib/utils.ts/** * Class helpers used by every styled item. They come from `@ndop/core`; * this file is yours, so it is the place to customize them, for example by * wrapping `cn` with an extended tailwind-merge configuration for your own * utilities. * * @packageDocumentation */ export { cn, variants, type ClassValue, type VariantOf } from '@ndop/core';Update the import paths to match your project
The files import each other through the
@/uiand@/libaliases.
Usage
import { Button } from '@/ui/button/button';
import {
Tooltip,
TooltipPortal,
TooltipTrigger,
TooltipContent,
} from '@/ui/tooltip/tooltip';<ng-container ndTooltip>
<button ndButton ndTooltipTrigger variant="outline">Hover</button>
<ng-template ndTooltipPortal>
<div ndTooltipContent>Add to library</div>
</ng-template>
</ng-container>Examples
Tooltip icon button
An icon-only button with its own accessible name and a tooltip on the end side for sighted users.
import { Component } from '@angular/core';
import { NdIcon } from '@ndop/core';
import { Button } from '@/ui/button/button';
import {
Tooltip,
TooltipPortal,
TooltipTrigger,
TooltipContent,
} from '@/ui/tooltip/tooltip';
/**
* An icon-only button with its own accessible name and a tooltip on the
* end side for sighted users.
*/
@Component({
selector: 'example-tooltip-icon-button',
imports: [
Tooltip,
TooltipTrigger,
TooltipPortal,
TooltipContent,
Button,
NdIcon,
],
template: `
<ng-container ndTooltip [openDelay]="300">
<button
ndButton
ndTooltipTrigger
variant="ghost"
size="icon"
aria-label="Dismiss notification"
>
<svg ndIcon="x"></svg>
</button>
<ng-template ndTooltipPortal>
<div ndTooltipContent side="end">Dismiss</div>
</ng-template>
</ng-container>
`,
})
export class TooltipIconButtonExample {}
Tooltip sides
Tooltips on each side of their triggers.
import { Component } from '@angular/core';
import type { NdSide } from '@ndop/primitives/tooltip';
import { Button } from '@/ui/button/button';
import {
Tooltip,
TooltipPortal,
TooltipTrigger,
TooltipContent,
} from '@/ui/tooltip/tooltip';
/**
* Tooltips on each side of their triggers. `start` and `end` follow the
* reading direction, so they swap in right-to-left layouts.
*/
@Component({
selector: 'example-tooltip-sides',
imports: [Button, Tooltip, TooltipPortal, TooltipTrigger, TooltipContent],
template: `
<div class="flex flex-wrap gap-2">
@for (side of sides; track side) {
<ng-container ndTooltip>
<button ndButton ndTooltipTrigger variant="outline">
{{ side }}
</button>
<ng-template ndTooltipPortal>
<div ndTooltipContent [side]="side">On the {{ side }} side</div>
</ng-template>
</ng-container>
}
</div>
`,
})
export class TooltipSidesExample {
protected readonly sides: NdSide[] = ['top', 'end', 'bottom', 'start'];
}
Accessibility
Implements the Tooltip pattern of the WAI-ARIA Authoring Practices.
Keyboard
Keys the component handles.
| Key | Action |
|---|---|
| Tab | Focusing the trigger shows the tooltip; moving focus away hides it. |
| Escape | Hides the tooltip without moving focus. |
Focus
- The tooltip never takes focus; focus stays on the trigger.
Right-to-left
As in Popover's spec.md: start and end sides and alignments swap in RTL.
API reference
Tooltip
[ndTooltip]A tooltip (@ndop/primitives/tooltip): a short description of its
trigger, shown after a delay on hover and at once on keyboard focus. Put
it on an ng-container.
| Tooltip binding | Type | Default |
|---|---|---|
[defaultOpen]Initial open state when open is not bound. | boolean | false |
[openDelay]Delay before opening on hover, in milliseconds. | number | 700 |
[closeDelay]Delay before closing once the pointer has left, in milliseconds. | number | 300 |
[(open)]Whether the tooltip is open. Two-way bindable with [(open)]. | boolean | false |
TooltipTrigger
[ndTooltipTrigger]The element its tooltip describes, which shows it on hover and focus.
Manages aria-describedby.
| Data attribute | Values |
|---|---|
data-slot | tooltip-trigger |
data-state | — |
TooltipPortal
ng-template[ndTooltipPortal]Marks the ng-template its tooltip renders while open.
TooltipContent
[ndTooltipContent]role tooltipThe tooltip bubble.
| TooltipContent binding | Type | Default |
|---|---|---|
[class]Extra classes, merged last so they win. | string | '' |
[side]Preferred side of the trigger; start and end swap in RTL. Defaults
to top, as in Radix and shadcn. | 'bottom' | 'top' | 'start' | 'end' | 'top' |
[align]Preferred alignment along that side. | 'start' | 'end' | 'center' | 'center' |
[sideOffset]Gap from the trigger, in pixels. | number | 4 |
| Data attribute | Values |
|---|---|
data-slot | tooltip-content |
data-state | — |
data-side | — |
data-align | — |
Agent notes
Do
- Use tooltips for brief, supplementary text, such as the name of an icon-only button that also has an
aria-label. - Keep the text to a few words.
Don't
- Don't put essential information only in a tooltip; touch devices can't hover.
- Don't put interactive content in a tooltip; use a popover.
- Don't add a tooltip to a disabled button; it can't receive focus.