Usage
Tooltip displays informative text when users hover, focus, or click a trigger. Each framework delivers it in the shape it is built for:
- React exports a
Tooltipcomponent that wraps a single trigger child, or targets one throughtargetRef. - Angular exports a
Tooltipdirective,[udxTooltip], that you put directly on the trigger. It injects its ownElementRef, so there is no target to hand over. Its inputs are prefixedudxTooltip*because the host element is not the directive’s own. - Svelte exports a
tooltipattachment,{@attach tooltip(() => ({ … }))}, that you put directly on the trigger — the Svelte counterpart of the Angular directive. Its options are read through a function, so a change updates the open tooltip in place.
The concept, the vocabulary, the behavior and the accessibility are identical; only the way you reach them differs.
import { Tooltip } from "@udixio/ui-react"Use trigger (“hover”, “focus”, “click”, or an array of these) to control how the tooltip appears, and position to place it relative to its target.
import { Button, Tooltip } from '@udixio/ui-react';
export default function TooltipBasicReact() {
return (
<div className="flex flex-wrap items-center gap-6 p-8">
<Tooltip text="Copy to clipboard">
<Button label="Hover me" />
</Tooltip>
<Tooltip text="Opens on click" trigger="click">
<Button label="Click me" />
</Tooltip>
<Tooltip text="Top placement" position="top">
<Button label="Top" />
</Tooltip>
</div>
);
}import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Button, Tooltip } from '@udixio/ui-angular';
@Component({
selector: 'docs-tooltip-basic-angular',
standalone: true,
imports: [Button, Tooltip],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-wrap items-center gap-6 p-8">
<udx-button label="Hover me" udxTooltip="Copy to clipboard" />
<udx-button
label="Click me"
udxTooltip="Opens on click"
udxTooltipTrigger="click"
/>
<udx-button
label="Top"
udxTooltip="Top placement"
udxTooltipPosition="top"
/>
</div>
`,
})
export class TooltipBasicAngular {}<script lang="ts">
import { Button, tooltip } from '@udixio/ui-svelte';
</script>
<div class="flex flex-wrap items-center gap-6 p-8">
<Button label="Hover me" {@attach tooltip(() => ({ text: 'Copy to clipboard' }))} />
<Button label="Click me" {@attach tooltip(() => ({ text: 'Opens on click', trigger: 'click' }))} />
<Button label="Top" {@attach tooltip(() => ({ text: 'Top placement', position: 'top' }))} />
</div>Rich content
variant="rich" renders a card-like surface with a title, text, and optional buttons. Supply custom content instead – content in React, a TemplateRef passed to udxTooltipContent in Angular, a snippet passed to content in Svelte – to replace that built-in layout entirely; the layout only renders when no custom content is given.
import { Button, Tooltip } from '@udixio/ui-react';
export default function TooltipRichReact() {
return (
<div className="flex flex-wrap items-center gap-6 p-8">
<Tooltip
variant="rich"
title="Saved"
text="Item added to favorites"
buttons={[{ label: 'Undo' }]}
>
<Button label="Rich tooltip" />
</Tooltip>
<Tooltip
variant="rich"
content={
<div>
<strong className="text-title-small">Shortcuts</strong>
<p className="text-body-medium">Press Cmd+K to open the command palette.</p>
</div>
}
>
<Button label="Custom content" />
</Tooltip>
</div>
);
}import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Button, Tooltip } from '@udixio/ui-angular';
@Component({
selector: 'docs-tooltip-rich-angular',
standalone: true,
imports: [Button, Tooltip],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-wrap items-center gap-6 p-8">
<udx-button
label="Rich tooltip"
udxTooltipVariant="rich"
udxTooltipTitle="Saved"
udxTooltip="Item added to favorites"
[udxTooltipButtons]="{ label: 'Undo' }"
/>
<ng-template #shortcuts>
<strong class="text-title-small">Shortcuts</strong>
<p class="text-body-medium">Press Cmd+K to open the command palette.</p>
</ng-template>
<udx-button
label="Custom content"
udxTooltipVariant="rich"
[udxTooltipContent]="shortcuts"
/>
</div>
`,
})
export class TooltipRichAngular {}<script lang="ts">
import { Button, tooltip } from '@udixio/ui-svelte';
</script>
{#snippet shortcuts()}
<strong class="text-title-small">Shortcuts</strong>
<p class="text-body-medium">Press Cmd+K to open the command palette.</p>
{/snippet}
<div class="flex flex-wrap items-center gap-6 p-8">
<Button
label="Rich tooltip"
{@attach tooltip(() => ({
variant: 'rich',
title: 'Saved',
text: 'Item added to favorites',
buttons: { label: 'Undo' },
}))}
/>
<Button label="Custom content" {@attach tooltip(() => ({ variant: 'rich', content: shortcuts }))} />
</div>Controlled state
Use open with onOpenChange (React and Svelte), or udxTooltipOpen with udxTooltipOpenChange (Angular), to fully control visibility. defaultOpen initializes uncontrolled state.
import { useState } from 'react';
import { Button, Tooltip } from '@udixio/ui-react';
export default function TooltipControlledReact() {
const [open, setOpen] = useState(false);
return (
<div className="flex items-center gap-6 p-8">
<Tooltip text="Controlled tooltip" open={open} onOpenChange={setOpen}>
<Button label={open ? 'Hide' : 'Show'} onClick={() => setOpen(!open)} />
</Tooltip>
</div>
);
}import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { Button, Tooltip } from '@udixio/ui-angular';
@Component({
selector: 'docs-tooltip-controlled-angular',
standalone: true,
imports: [Button, Tooltip],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex items-center gap-6 p-8">
<udx-button
[label]="open() ? 'Hide' : 'Show'"
(click)="open.set(!open())"
udxTooltip="Controlled tooltip"
[udxTooltipOpen]="open()"
(udxTooltipOpenChange)="open.set($event)"
/>
</div>
`,
})
export class TooltipControlledAngular {
protected readonly open = signal(false);
}<script lang="ts">
import { Button, tooltip } from '@udixio/ui-svelte';
let open = $state(false);
</script>
<div class="flex items-center gap-6 p-8">
<Button
label={open ? 'Hide' : 'Show'}
onclick={() => (open = !open)}
{@attach tooltip(() => ({
text: 'Controlled tooltip',
open,
onOpenChange: (next) => (open = next),
}))}
/>
</div>Migrating from <udx-tooltip>
The Angular adapter was a udx-tooltip component taking a required target input. It is removed, with no compatibility alias. Move it onto the trigger and drop the target:
<!-- before -->
<udx-button #trigger label="Save" />
<udx-tooltip [target]="triggerRef()" text="Save the draft" />
<!-- after -->
<udx-button label="Save" udxTooltip="Save the draft" />Every other input keeps its meaning under a udxTooltip* alias, and <ng-content> becomes a TemplateRef on udxTooltipContent. The viewChild and ElementRef plumbing the old form required disappears entirely.
Delays
Use openDelay and closeDelay (ms) to tune the hover/focus behavior – udxTooltipOpenDelay and udxTooltipCloseDelay in Angular. Defaults: 400ms and 150ms. Focus and click transitions are immediate. On touch, a long press opens the tooltip after 500ms; after release or cancellation, it remains visible for 1.5 seconds as specified by Material 3.
One shared implementation
Two controllers in @udixio/core/dom carry the behavior, and both adapters drive them rather than reimplementing anything:
createTooltipTriggerControllerowns the timers, the pointer, keyboard and touch wiring, the touch long press,aria-describedby, and the arbitration that closes one tooltip when another opens.createTooltipTransitionControllerowns the open/close opacity and height transition, with Anime.js.
React, Angular and Svelte therefore share identical timing, reduced-motion behavior, interruption handling and cleanup, by construction rather than by discipline. Override the transition timing with transition (udxTooltipTransition), taking { duration, ease }.
Position
Tooltip renders through AnchorPositioner, which uses native CSS Anchor Positioning where supported and falls back to tracking the target’s getBoundingClientRect() otherwise. position accepts top, bottom, left, right, top-left, top-right, bottom-left, or bottom-right; it defaults to bottom-right for variant="rich" and bottom otherwise.
The four single keywords sit on one side of the target and stay centred on the other axis. The four corners name a cell of the 3x3 grid around the target, so they sit diagonally outside its box on both axes: bottom-right places the tooltip below the target’s bottom edge and past its right edge – not below it, right-aligned. Both the native and the fallback path resolve them the same way.
Keyboard
| Keys | Actions |
|---|---|
| Tab | Focus lands on the target element, if focusable |
| Escape | Closes an open tooltip |
Accessibility
Only one tooltip is visible at a time. Opening a tooltip dismisses the previously visible tooltip, including when controlled tooltips are used.
- Provides
role="tooltip"and links to its target viaaria-describedbywhen open. - The tooltip surface stays mounted at all times, hidden with
inertandaria-hiddenrather than unmounted, so it can animate out; expensive custom content is not torn down untilTooltipitself unmounts.