Skip to main content

Tooltip

Non-interactive contextual hint shown on hover or focus. Wraps any trigger with the mates tooltip() directive, automatically positions above/below the anchor, and escapes overflow:hidden containers via portal mounting.

Quick start
import { tooltip } from "mates-ui";
import { html } from "mates";

// Attach to any element as an attribute directive
html`<button ${tooltip("Save your changes")}>Save</button>`

// With options
html`<button ${tooltip("Save your changes", { delay: 200, maxWidth: "14rem" })}>Save</button>`

// With rich content
html`<button ${tooltip(html`Press <kbd>Ctrl+S</kbd>`)}>Save</button>`

Basic

import { html, renderApp } from 'mates';
import { FilledButton, OutlinedButton, TextButton, tooltip } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-3)">
<span ${tooltip("Save your changes")}>${FilledButton("Save")}</span>
<span ${tooltip("Share with teammates")}>${OutlinedButton("Share")}</span>
<span ${tooltip("Download file")}>${TextButton("Download")}</span>
</x-row>
`;

renderApp(App, document.getElementById('app'));

On icon buttons

import { html, renderApp } from 'mates';
import { IconButton, tooltip } from 'mates-ui';
import { DeleteIcon, EditIcon, SettingsIcon, ShareIcon } from 'mates-icons';

const App = () => () => html`
<x-row gap="var(--md-space-3)">
<span ${tooltip("Edit")}>${IconButton({ icon: EditIcon(), variant: "standard" })}</span>
<span ${tooltip("Delete permanently")}>${IconButton({ icon: DeleteIcon(), variant: "standard" })}</span>
<span ${tooltip("Share")}>${IconButton({ icon: ShareIcon(), variant: "filled" })}</span>
<span ${tooltip("Settings")}>${IconButton({ icon: SettingsIcon(), variant: "outlined" })}</span>
</x-row>
`;

renderApp(App, document.getElementById('app'));

Custom delay

import { html, renderApp } from 'mates';
import { OutlinedButton, tooltip } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-3)">
<span ${tooltip("No delay (0ms)", { delay: 0 })}>${OutlinedButton("Instant")}</span>
<span ${tooltip("Default delay (200ms)" )}>${OutlinedButton("Default")}</span>
<span ${tooltip("Slow delay (800ms)", { delay: 800 })}>${OutlinedButton("Slow")}</span>
</x-row>
`;

renderApp(App, document.getElementById('app'));

Rich content

import { html, renderApp } from 'mates';
import { DangerButton, OutlinedButton, tooltip } from 'mates-ui';
import { InfoIcon, WarningIcon } from 'mates-icons';

const App = () => () => html`
<x-row gap="var(--md-space-3)">
<span ${tooltip(html`<div style="line-height:1.5;"><strong style="display:block;margin-bottom:0.25rem;">Pro tip</strong>Use <code style="background:rgba(255,255,255,0.15);padding:0 0.25rem;border-radius:3px;">Ctrl+S</code> to save quickly.</div>`, { maxWidth: "14rem", padding: "0.5rem 0.75rem" })}>
${OutlinedButton("Keyboard shortcut")}
</span>
<span ${tooltip(html`<div style="line-height:1.5;"><strong style="display:block;margin-bottom:0.25rem;">Warning</strong>This action cannot be undone.</div>`, { maxWidth: "12rem", padding: "0.5rem 0.75rem" })}>
${DangerButton("Delete")}
</span>
</x-row>
`;

renderApp(App, document.getElementById('app'));

On inline text

import { html, renderApp } from 'mates';
import { Badge, tooltip } from 'mates-ui';

const App = () => () => html`
<p style="font-size:var(--md-text-base);line-height:1.7;max-width:38rem;">
Attach tooltips to
<span ${tooltip("Any inline element works as a trigger")} style="text-decoration:underline dotted;cursor:help;color:var(--md-color-primary);">any inline element</span>,
including
<span ${tooltip("Badges are great tooltip triggers")} style="display:inline-flex;">${Badge("NEW")}</span>
badges. Hover to see the tooltip.
</p>
`;

renderApp(App, document.getElementById('app'));

On avatars and chips

import { html, renderApp } from 'mates';
import { Avatar, Chip, IconButton, tooltip } from 'mates-ui';
import { HelpIcon, PersonIcon } from 'mates-icons';

const App = () => () => html`
<x-row gap="var(--md-space-4)" align="center">
<span ${tooltip("Alice Johnson — Product Designer")}>${Avatar({ name: "Alice Johnson", size: "md", status: "online" })}</span>
<span ${tooltip("Bob Chen — Frontend Engineer")}>${Avatar({ name: "Bob Chen", size: "md" })}</span>
<span ${tooltip("Assigned to this task")}>${Chip("Design", { variant: "assist", icon: PersonIcon() })}</span>
<span ${tooltip("Help & documentation")}>${IconButton({ icon: HelpIcon(), variant: "standard" })}</span>
</x-row>
`;

renderApp(App, document.getElementById('app'));

Raw directive

import { html, renderApp } from 'mates';
import { tooltip } from 'mates-ui';
import { InfoIcon } from 'mates-icons';

const App = () => () => html`
<x-row gap="var(--md-space-3)" align="center">
<button ${tooltip("Native button via directive")}
style="padding:0.5rem 1rem;border:1px solid var(--md-color-border);border-radius:var(--md-radius-md);background:var(--md-surface-card);color:var(--md-on-card);cursor:pointer;font-size:var(--md-text-sm);">
Native button
</button>
<span ${tooltip("Tooltip on a plain span")}
style="padding:0.5rem 1rem;background:var(--md-surface-panel);border-radius:var(--md-radius-md);cursor:default;font-size:var(--md-text-sm);">
Plain span
</span>
<div ${tooltip("Icon via directive")}
style="display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border:1px solid var(--md-color-border);border-radius:var(--md-radius-md);cursor:pointer;">
${InfoIcon({ size: 20 })}
</div>
</x-row>
`;

renderApp(App, document.getElementById('app'));

No-wrap single line

import { html, renderApp } from 'mates';
import { OutlinedButton, tooltip } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-3)">
<span ${tooltip("A single-line tooltip that never wraps", { whiteSpace: "nowrap" })}>${OutlinedButton("Hover me")}</span>
<span ${tooltip("Short", { whiteSpace: "nowrap" })}>${OutlinedButton("Short tip")}</span>
</x-row>
`;

renderApp(App, document.getElementById('app'));

Display modes

import { html, renderApp } from 'mates';
import { OutlinedButton, TonalButton, Text, tooltip } from 'mates-ui';

const App = () => () => html`
<x-col gap="var(--md-space-4)" align="flex-start">
<x-row gap="var(--md-space-3)" align="center">
${Text("inline-flex (default):", { type: "label-md", color: "muted" })}
<span style="display:inline-flex;" ${tooltip("inline-flex anchor")}>${TonalButton("Trigger")}</span>
</x-row>
<x-row gap="var(--md-space-3)" align="center">
${Text("inline-block:", { type: "label-md", color: "muted" })}
<span style="display:inline-block;" ${tooltip("inline-block anchor")}>
<span style="padding:0.25rem 0.75rem;background:var(--md-surface-panel);border-radius:var(--md-radius-md);font-size:var(--md-text-sm);cursor:default;">Span trigger</span>
</span>
</x-row>
<x-row gap="var(--md-space-3)" align="center" style="width:100%;max-width:24rem;">
${Text("block:", { type: "label-md", color: "muted" })}
<span style="display:block;flex:1;" ${tooltip("Block anchor")}>${OutlinedButton("Full width", { fullWidth: true })}</span>
</x-row>
</x-col>
`;

renderApp(App, document.getElementById('app'));

Show on click

import { html, renderApp } from 'mates';
import { FilledButton, IconButton, OutlinedButton, tooltip } from 'mates-ui';
import { HelpIcon, InfoIcon } from 'mates-icons';

const App = () => () => html`
<x-row gap="var(--md-space-3)">
<span ${tooltip("Clicked! Click outside or click again to close.", { showOnClick: true })}>${FilledButton("Click me")}</span>
<span ${tooltip(html`<div style="line-height:1.6;"><strong>More info</strong><br>Stays open until you click outside.</div>`, { showOnClick: true, maxWidth: "14rem", whiteSpace: "normal", padding: "0.5rem 0.75rem" })}>
${OutlinedButton("Details")}
</span>
<span ${tooltip("Icon tooltip on click", { showOnClick: true })}>${IconButton({ icon: HelpIcon(), variant: "standard" })}</span>
</x-row>
`;

renderApp(App, document.getElementById('app'));

Tooltip

import { tooltip } from "mates-ui";
Prop Type Default Description
attr AttrMap HTML attribute overrides on the anchor wrapper.
classes string Extra CSS class applied to the anchor wrapper.
classes string | string[] Extra root classes — joined and appended after built-in classes.
content* string | TemplateResult Tooltip body text or rich HTML template.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
display "inline-flex" | "inline-block" | "block" "inline-flex" Display mode of the anchor wrapper element.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
style StyleMap Inline CSS on the root element.
testId string "md-tooltip-anchor" data-testid override for the anchor wrapper.
tip.borderRadius string var(--md-radius-sm) Border radius of the tooltip bubble.
tip.delay number 200 Delay in ms before the tooltip appears.
tip.fontSize string var(--md-text-sm) Font size inside the tooltip.
tip.maxWidth string "min(18.75rem, 85vw)" Maximum width of the tooltip bubble.
tip.padding string "0.38rem 0.75rem" Padding inside the tooltip bubble.
tip.style Record<string, string> Additional inline styles merged onto the tooltip bubble. Useful for custom background or color.
tip.whiteSpace string CSS white-space value for the tooltip text (e.g. 'nowrap').
trigger* TemplateResult The element that activates the tooltip on hover or focus.

tooltip

Prop Type Default Description
tooltip(content, tip?) DirectiveResult Raw mates directive for attaching a tooltip directly to an existing element via a lit-html template binding. Same tip options as the Tooltip component.

showOnClick behaviour

Prop Type Default Description
tip.showOnClick boolean false When true, the tooltip is triggered by clicking the anchor instead of hovering or focusing it. A second click on the anchor OR a click anywhere outside both hide the tooltip. Delay defaults to 0 in this mode.

CSS tokens

Token Role
--md-font-family Font family
--md-on-tooltip Tooltip text colour
--md-radius-sm Default border radius
--md-shadow-dropdown Box shadow on the bubble
--md-surface-tooltip Tooltip background colour
--md-text-sm Default font size

Caveats

  • Tooltips escape overflow:hidden containers — they are portal-mounted.
  • The tooltip is positioned automatically above or below the trigger depending on available space.
  • Tooltips are accessible: they are associated with the trigger via aria-describedby.
  • Do not put interactive content (buttons, links) inside a tooltip — use a Popover instead.