API
Tooltip
A hint on hover or focus, through interest invokers.
Installation
pnpm dlx zazz-ui add tooltipnpx zazz-ui add tooltipyarn dlx zazz-ui add tooltipbunx zazz-ui add tooltipAlso adds kbd, popover, which it depends on. Run zazz-ui init first if the project has no zazz/ folder yet.
Examples
Default
Add to library
<ui-tooltip>
<button type="button" data-ui="button" interestfor="tooltip-example-default-1">Hover</button>
<div data-tooltip-slot="content" id="tooltip-example-default-1" popover="hint">
Add to library
<span data-tooltip-slot="arrow" aria-hidden="true"></span>
</div>
</ui-tooltip>/**
* tooltip.css: Tooltip (ui-tooltip | [data-ui~="tooltip"]; slots data-tooltip-slot="trigger | content | arrow")
*
* @layer variables, reset, components
* @requires _layers.css, _variables.css, _properties.css (--_*-resolved), popover.css, kbd.css (--ui-kbd-*)
* @uses interestfor: Interest Invokers API (Chromium 142+; polyfill via invokers)
* @uses data-tooltip-slot="content": hint popover (light dismiss, auto-hides sibling hints)
* @uses anchor-name, position-anchor, position-area, position-try-fallbacks:
* explicit anchors for polyfill; data-popover-side maps to --ui-popover-position-*
* @tokens --ui-tooltip-* (@layer variables; read on the content slot)
*/
@layer variables {
:root {
--ui-tooltip-bg: var(--color-foreground);
--ui-tooltip-text: var(--color-background);
--ui-tooltip-border-color: transparent;
--ui-tooltip-rounded: var(--radius-sm);
--ui-tooltip-shadow: var(--shadow-sm);
--ui-tooltip-px: var(--space-xs);
--ui-tooltip-py: var(--spacing);
--ui-tooltip-gap: var(--spacing);
--ui-tooltip-font-family: var(--font-family-mono);
--ui-tooltip-font-size: var(--font-size-xs);
--ui-tooltip-leading: var(--leading-xs);
--ui-tooltip-font-weight: var(--font-weight-body);
--ui-tooltip-max-w: 16ch;
/* Arrow */
--ui-tooltip-arrow-size: calc(var(--spacing) * 2);
--ui-tooltip-arrow-color: var(--ui-tooltip-bg);
--ui-tooltip-arrow-radius: calc(var(--spacing) * 0.5);
/* Reveal transitions: lighter than popover */
--ui-tooltip-transition-duration: calc(var(--default-transition-duration) / 2);
--ui-tooltip-transition-timing-function: var(--default-transition-timing-function);
--ui-tooltip-transition-delay: 0ms;
--ui-tooltip-transition:
opacity var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay),
transform var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay),
overlay var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay) allow-discrete,
display var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay) allow-discrete;
--ui-tooltip-scale--start: 0.94;
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-tooltip-opacity--start: 0;
--ui-tooltip-scale--end: 1;
--ui-tooltip-translate--end: 0, 0, 0;
--ui-tooltip-opacity--end: 1;
/* Positioning: smaller offset than popover menus */
--ui-tooltip-my: var(--spacing);
--ui-tooltip-mx: 0;
}
}
/* Popover overrides, on the tooltip content (declared on :root they would retheme
every popover in the kit, since tooltip.css loads after popover.css). */
@layer ui.components {
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] {
--ui-popover-shadow: var(--shadow-xs);
--ui-popover-min-w: 0; /* dual-mode: no minimum (fit-content cannot pass the typed pair) */
--ui-popover-position-area: block-start span-all;
--ui-popover-origin: bottom center;
}
}
@layer reset {
/* ===========================================================================
HINT POPOVER OVERRIDES: popover.css targets menus; hints need a compact
surface without min-inline-size or backdrop blur.
=========================================================================== */
:where(ui-tooltip, [data-ui~="tooltip"]) :where([data-tooltip-slot~="content"]) {
color: var(--_text-resolved, var(--ui-tooltip-text));
background-color: var(--_bg-resolved, var(--ui-tooltip-bg));
min-inline-size: unset;
--_max-w-len: var(--_max-w-resolved, var(--ui-tooltip-max-w));
--_max-w-num: var(--_max-w-resolved, var(--ui-tooltip-max-w));
max-inline-size: calc(var(--_max-w-len) + var(--_max-w-num) * var(--spacing));
backdrop-filter: none;
transform-origin: var(--ui-popover-origin);
opacity: var(--ui-tooltip-opacity--start);
transform: scale(var(--ui-tooltip-scale--start)) translate3d(var(--ui-tooltip-translate--start));
transition: var(--ui-tooltip-transition);
}
@supports (anchor-name: --zazz) {
:where(ui-tooltip, [data-ui~="tooltip"]) :where([data-tooltip-slot~="content"]) {
--_my-len: var(--_my-resolved, var(--ui-tooltip-my));
--_my-num: var(--_my-resolved, var(--ui-tooltip-my));
margin-block: calc(var(--_my-len) + var(--_my-num) * var(--spacing));
--_mx-len: var(--_mx-resolved, var(--ui-tooltip-mx));
--_mx-num: var(--_mx-resolved, var(--ui-tooltip-mx));
margin-inline: calc(var(--_mx-len) + var(--_mx-num) * var(--spacing));
}
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"]:popover-open {
display: flex;
opacity: var(--ui-tooltip-opacity--end);
transform: scale(var(--ui-tooltip-scale--end)) translate3d(var(--ui-tooltip-translate--end));
@starting-style {
opacity: var(--ui-tooltip-opacity--start);
transform: scale(var(--ui-tooltip-scale--start))
translate3d(var(--ui-tooltip-translate--start));
}
}
}
@layer ui.components {
/* ===========================================================================
TOOLTIP: interestfor sets the ARIA (aria-describedby / aria-details), so do
not add role="tooltip". Explicit anchor-name is needed because the polyfill
does not provide implicit anchors.
=========================================================================== */
:where(ui-tooltip, [data-ui~="tooltip"]) {
anchor-scope: --ui-tooltip-trigger;
display: inline-block;
}
:where(ui-tooltip, [data-ui~="tooltip"]) > :where([interestfor], [data-tooltip-slot~="trigger"]) {
anchor-name: --ui-tooltip-trigger;
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="trigger"] {
display: inline-flex;
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] {
position-anchor: --ui-tooltip-trigger;
display: flex;
align-items: center;
--_gap-len: var(--_gap-resolved, var(--ui-tooltip-gap));
--_gap-num: var(--_gap-resolved, var(--ui-tooltip-gap));
gap: calc(var(--_gap-len) + var(--_gap-num) * var(--spacing));
inline-size: max-content;
--_px-len: var(--_px-resolved, var(--ui-tooltip-px));
--_px-num: var(--_px-resolved, var(--ui-tooltip-px));
padding-inline: calc(var(--_px-len) + var(--_px-num) * var(--spacing));
--_py-len: var(--_py-resolved, var(--ui-tooltip-py));
--_py-num: var(--_py-resolved, var(--ui-tooltip-py));
padding-block: calc(var(--_py-len) + var(--_py-num) * var(--spacing));
border: 1px solid var(--_border-color-resolved, var(--ui-tooltip-border-color));
border-radius: var(--_rounded-resolved, var(--ui-tooltip-rounded));
box-shadow: var(--_shadow-resolved, var(--ui-tooltip-shadow));
font-family: var(--_font-family-resolved, var(--ui-tooltip-font-family));
font-size: var(--_font-size-resolved, var(--ui-tooltip-font-size));
line-height: var(--_leading-resolved, var(--ui-tooltip-leading));
font-weight: var(--_font-weight-resolved, var(--ui-tooltip-font-weight));
text-wrap: balance;
}
/* Descendant (not child) so keys wrapped in a ui-kbd-group still retint */
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] :where(kbd) {
--ui-kbd-bg: color-mix(in oklch, var(--ui-tooltip-text) 15%, transparent);
--ui-kbd-text: var(--ui-tooltip-text);
--ui-kbd-min-w: calc(var(--spacing) * 4);
--ui-kbd-min-h: calc(var(--spacing) * 4);
--ui-kbd-font-size: var(--font-size-xs);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"]:has([data-tooltip-slot~="arrow"]) {
overflow: visible;
}
/* Positioning: reuse --ui-popover-position-* hooks from popover.css via data-popover-side */
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-popover-position-area: block-start span-all;
--ui-popover-origin: bottom center;
--ui-tooltip-my: var(--spacing);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-popover-position-area: block-end span-all;
--ui-popover-origin: top center;
--ui-tooltip-my: var(--spacing);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0.25em, 0, 0;
--ui-popover-position-area: inline-start span-all;
--ui-popover-origin: center right;
--ui-tooltip-mx: var(--spacing);
--ui-tooltip-my: 0;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0.25em, 0, 0;
--ui-popover-position-area: inline-end span-all;
--ui-popover-origin: center left;
--ui-tooltip-mx: var(--spacing);
--ui-tooltip-my: 0;
}
/* Tooltip arrow */
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="arrow"] {
position: absolute;
/* physical top/left/right/bottom throughout the arrow rules: they pair with
the physical [data-popover-side] placement semantics from anchor positioning */
top: calc(100% - var(--ui-tooltip-arrow-size) / 2);
left: 50%;
transform: translate(-50%, 0%) rotate(45deg);
inline-size: var(--ui-tooltip-arrow-size);
block-size: var(--ui-tooltip-arrow-size);
background-color: var(--ui-tooltip-arrow-color);
border-radius: var(--ui-tooltip-arrow-radius);
pointer-events: none;
}
/* Tooltip arrow position */
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"]
[data-tooltip-slot~="arrow"] {
bottom: calc((var(--ui-tooltip-arrow-size) / 2) * -1);
left: 50%;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"]
[data-tooltip-slot~="arrow"] {
top: calc((var(--ui-tooltip-arrow-size) / 2) * -1);
left: 50%;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"]
[data-tooltip-slot~="arrow"] {
left: calc(100% - var(--ui-tooltip-arrow-size) / 2);
top: 50%;
transform: translate(0%, -50%) rotate(45deg);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"]
[data-tooltip-slot~="arrow"] {
right: calc(100% - var(--ui-tooltip-arrow-size) / 2);
left: auto;
top: 50%;
transform: translate(0%, -50%) rotate(45deg);
}
}With kbd
Save
⌘
S
<ui-tooltip>
<button
type="button"
data-ui="button"
data-button-variant="muted"
data-button-size="icon"
interestfor="tooltip-example-save-1"
aria-label="Save"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none" />
<path
fill="currentColor"
d="M74.34,85.66A8,8,0,0,1,85.66,74.34L120,108.69V24a8,8,0,0,1,16,0v84.69l34.34-34.35a8,8,0,0,1,11.32,11.32l-48,48a8,8,0,0,1-11.32,0ZM240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H84.4a4,4,0,0,1,2.83,1.17L111,145A24,24,0,0,0,145,145l23.8-23.8A4,4,0,0,1,171.6,120H224A16,16,0,0,1,240,136Zm-40,32a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"
/>
</svg>
</button>
<div data-tooltip-slot="content" id="tooltip-example-save-1" popover="hint">
Save
<ui-kbd-group>
<kbd>⌘</kbd>
<kbd>S</kbd>
</ui-kbd-group>
<span data-tooltip-slot="arrow" aria-hidden="true"></span>
</div>
</ui-tooltip>/**
* tooltip.css: Tooltip (ui-tooltip | [data-ui~="tooltip"]; slots data-tooltip-slot="trigger | content | arrow")
*
* @layer variables, reset, components
* @requires _layers.css, _variables.css, _properties.css (--_*-resolved), popover.css, kbd.css (--ui-kbd-*)
* @uses interestfor: Interest Invokers API (Chromium 142+; polyfill via invokers)
* @uses data-tooltip-slot="content": hint popover (light dismiss, auto-hides sibling hints)
* @uses anchor-name, position-anchor, position-area, position-try-fallbacks:
* explicit anchors for polyfill; data-popover-side maps to --ui-popover-position-*
* @tokens --ui-tooltip-* (@layer variables; read on the content slot)
*/
@layer variables {
:root {
--ui-tooltip-bg: var(--color-foreground);
--ui-tooltip-text: var(--color-background);
--ui-tooltip-border-color: transparent;
--ui-tooltip-rounded: var(--radius-sm);
--ui-tooltip-shadow: var(--shadow-sm);
--ui-tooltip-px: var(--space-xs);
--ui-tooltip-py: var(--spacing);
--ui-tooltip-gap: var(--spacing);
--ui-tooltip-font-family: var(--font-family-mono);
--ui-tooltip-font-size: var(--font-size-xs);
--ui-tooltip-leading: var(--leading-xs);
--ui-tooltip-font-weight: var(--font-weight-body);
--ui-tooltip-max-w: 16ch;
/* Arrow */
--ui-tooltip-arrow-size: calc(var(--spacing) * 2);
--ui-tooltip-arrow-color: var(--ui-tooltip-bg);
--ui-tooltip-arrow-radius: calc(var(--spacing) * 0.5);
/* Reveal transitions: lighter than popover */
--ui-tooltip-transition-duration: calc(var(--default-transition-duration) / 2);
--ui-tooltip-transition-timing-function: var(--default-transition-timing-function);
--ui-tooltip-transition-delay: 0ms;
--ui-tooltip-transition:
opacity var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay),
transform var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay),
overlay var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay) allow-discrete,
display var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay) allow-discrete;
--ui-tooltip-scale--start: 0.94;
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-tooltip-opacity--start: 0;
--ui-tooltip-scale--end: 1;
--ui-tooltip-translate--end: 0, 0, 0;
--ui-tooltip-opacity--end: 1;
/* Positioning: smaller offset than popover menus */
--ui-tooltip-my: var(--spacing);
--ui-tooltip-mx: 0;
}
}
/* Popover overrides, on the tooltip content (declared on :root they would retheme
every popover in the kit, since tooltip.css loads after popover.css). */
@layer ui.components {
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] {
--ui-popover-shadow: var(--shadow-xs);
--ui-popover-min-w: 0; /* dual-mode: no minimum (fit-content cannot pass the typed pair) */
--ui-popover-position-area: block-start span-all;
--ui-popover-origin: bottom center;
}
}
@layer reset {
/* ===========================================================================
HINT POPOVER OVERRIDES: popover.css targets menus; hints need a compact
surface without min-inline-size or backdrop blur.
=========================================================================== */
:where(ui-tooltip, [data-ui~="tooltip"]) :where([data-tooltip-slot~="content"]) {
color: var(--_text-resolved, var(--ui-tooltip-text));
background-color: var(--_bg-resolved, var(--ui-tooltip-bg));
min-inline-size: unset;
--_max-w-len: var(--_max-w-resolved, var(--ui-tooltip-max-w));
--_max-w-num: var(--_max-w-resolved, var(--ui-tooltip-max-w));
max-inline-size: calc(var(--_max-w-len) + var(--_max-w-num) * var(--spacing));
backdrop-filter: none;
transform-origin: var(--ui-popover-origin);
opacity: var(--ui-tooltip-opacity--start);
transform: scale(var(--ui-tooltip-scale--start)) translate3d(var(--ui-tooltip-translate--start));
transition: var(--ui-tooltip-transition);
}
@supports (anchor-name: --zazz) {
:where(ui-tooltip, [data-ui~="tooltip"]) :where([data-tooltip-slot~="content"]) {
--_my-len: var(--_my-resolved, var(--ui-tooltip-my));
--_my-num: var(--_my-resolved, var(--ui-tooltip-my));
margin-block: calc(var(--_my-len) + var(--_my-num) * var(--spacing));
--_mx-len: var(--_mx-resolved, var(--ui-tooltip-mx));
--_mx-num: var(--_mx-resolved, var(--ui-tooltip-mx));
margin-inline: calc(var(--_mx-len) + var(--_mx-num) * var(--spacing));
}
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"]:popover-open {
display: flex;
opacity: var(--ui-tooltip-opacity--end);
transform: scale(var(--ui-tooltip-scale--end)) translate3d(var(--ui-tooltip-translate--end));
@starting-style {
opacity: var(--ui-tooltip-opacity--start);
transform: scale(var(--ui-tooltip-scale--start))
translate3d(var(--ui-tooltip-translate--start));
}
}
}
@layer ui.components {
/* ===========================================================================
TOOLTIP: interestfor sets the ARIA (aria-describedby / aria-details), so do
not add role="tooltip". Explicit anchor-name is needed because the polyfill
does not provide implicit anchors.
=========================================================================== */
:where(ui-tooltip, [data-ui~="tooltip"]) {
anchor-scope: --ui-tooltip-trigger;
display: inline-block;
}
:where(ui-tooltip, [data-ui~="tooltip"]) > :where([interestfor], [data-tooltip-slot~="trigger"]) {
anchor-name: --ui-tooltip-trigger;
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="trigger"] {
display: inline-flex;
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] {
position-anchor: --ui-tooltip-trigger;
display: flex;
align-items: center;
--_gap-len: var(--_gap-resolved, var(--ui-tooltip-gap));
--_gap-num: var(--_gap-resolved, var(--ui-tooltip-gap));
gap: calc(var(--_gap-len) + var(--_gap-num) * var(--spacing));
inline-size: max-content;
--_px-len: var(--_px-resolved, var(--ui-tooltip-px));
--_px-num: var(--_px-resolved, var(--ui-tooltip-px));
padding-inline: calc(var(--_px-len) + var(--_px-num) * var(--spacing));
--_py-len: var(--_py-resolved, var(--ui-tooltip-py));
--_py-num: var(--_py-resolved, var(--ui-tooltip-py));
padding-block: calc(var(--_py-len) + var(--_py-num) * var(--spacing));
border: 1px solid var(--_border-color-resolved, var(--ui-tooltip-border-color));
border-radius: var(--_rounded-resolved, var(--ui-tooltip-rounded));
box-shadow: var(--_shadow-resolved, var(--ui-tooltip-shadow));
font-family: var(--_font-family-resolved, var(--ui-tooltip-font-family));
font-size: var(--_font-size-resolved, var(--ui-tooltip-font-size));
line-height: var(--_leading-resolved, var(--ui-tooltip-leading));
font-weight: var(--_font-weight-resolved, var(--ui-tooltip-font-weight));
text-wrap: balance;
}
/* Descendant (not child) so keys wrapped in a ui-kbd-group still retint */
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] :where(kbd) {
--ui-kbd-bg: color-mix(in oklch, var(--ui-tooltip-text) 15%, transparent);
--ui-kbd-text: var(--ui-tooltip-text);
--ui-kbd-min-w: calc(var(--spacing) * 4);
--ui-kbd-min-h: calc(var(--spacing) * 4);
--ui-kbd-font-size: var(--font-size-xs);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"]:has([data-tooltip-slot~="arrow"]) {
overflow: visible;
}
/* Positioning: reuse --ui-popover-position-* hooks from popover.css via data-popover-side */
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-popover-position-area: block-start span-all;
--ui-popover-origin: bottom center;
--ui-tooltip-my: var(--spacing);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-popover-position-area: block-end span-all;
--ui-popover-origin: top center;
--ui-tooltip-my: var(--spacing);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0.25em, 0, 0;
--ui-popover-position-area: inline-start span-all;
--ui-popover-origin: center right;
--ui-tooltip-mx: var(--spacing);
--ui-tooltip-my: 0;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0.25em, 0, 0;
--ui-popover-position-area: inline-end span-all;
--ui-popover-origin: center left;
--ui-tooltip-mx: var(--spacing);
--ui-tooltip-my: 0;
}
/* Tooltip arrow */
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="arrow"] {
position: absolute;
/* physical top/left/right/bottom throughout the arrow rules: they pair with
the physical [data-popover-side] placement semantics from anchor positioning */
top: calc(100% - var(--ui-tooltip-arrow-size) / 2);
left: 50%;
transform: translate(-50%, 0%) rotate(45deg);
inline-size: var(--ui-tooltip-arrow-size);
block-size: var(--ui-tooltip-arrow-size);
background-color: var(--ui-tooltip-arrow-color);
border-radius: var(--ui-tooltip-arrow-radius);
pointer-events: none;
}
/* Tooltip arrow position */
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"]
[data-tooltip-slot~="arrow"] {
bottom: calc((var(--ui-tooltip-arrow-size) / 2) * -1);
left: 50%;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"]
[data-tooltip-slot~="arrow"] {
top: calc((var(--ui-tooltip-arrow-size) / 2) * -1);
left: 50%;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"]
[data-tooltip-slot~="arrow"] {
left: calc(100% - var(--ui-tooltip-arrow-size) / 2);
top: 50%;
transform: translate(0%, -50%) rotate(45deg);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"]
[data-tooltip-slot~="arrow"] {
right: calc(100% - var(--ui-tooltip-arrow-size) / 2);
left: auto;
top: 50%;
transform: translate(0%, -50%) rotate(45deg);
}
}Sides
Tooltip on left
Tooltip on top
Tooltip on bottom
Tooltip on right
<div
style="
--display: flex;
--flex-wrap: wrap;
--items: center;
--gap: 6;
"
>
<ui-tooltip>
<button type="button" data-ui="button" interestfor="tooltip-example-left-1">Left</button>
<div
data-tooltip-slot="content"
id="tooltip-example-left-1"
popover="hint"
data-popover-side="left"
>
Tooltip on left
<span data-tooltip-slot="arrow" aria-hidden="true"></span>
</div>
</ui-tooltip>
<ui-tooltip>
<button type="button" data-ui="button" interestfor="tooltip-example-top-1">Top</button>
<div
data-tooltip-slot="content"
id="tooltip-example-top-1"
popover="hint"
data-popover-side="top"
>
Tooltip on top
<span data-tooltip-slot="arrow" aria-hidden="true"></span>
</div>
</ui-tooltip>
<ui-tooltip>
<button type="button" data-ui="button" interestfor="tooltip-example-bottom-1">Bottom</button>
<div
data-tooltip-slot="content"
id="tooltip-example-bottom-1"
popover="hint"
data-popover-side="bottom"
>
Tooltip on bottom
<span data-tooltip-slot="arrow" aria-hidden="true"></span>
</div>
</ui-tooltip>
<ui-tooltip>
<button type="button" data-ui="button" interestfor="tooltip-example-right-1">Right</button>
<div
data-tooltip-slot="content"
id="tooltip-example-right-1"
popover="hint"
data-popover-side="right"
>
Tooltip on right
<span data-tooltip-slot="arrow" aria-hidden="true"></span>
</div>
</ui-tooltip>
</div>/**
* tooltip.css: Tooltip (ui-tooltip | [data-ui~="tooltip"]; slots data-tooltip-slot="trigger | content | arrow")
*
* @layer variables, reset, components
* @requires _layers.css, _variables.css, _properties.css (--_*-resolved), popover.css, kbd.css (--ui-kbd-*)
* @uses interestfor: Interest Invokers API (Chromium 142+; polyfill via invokers)
* @uses data-tooltip-slot="content": hint popover (light dismiss, auto-hides sibling hints)
* @uses anchor-name, position-anchor, position-area, position-try-fallbacks:
* explicit anchors for polyfill; data-popover-side maps to --ui-popover-position-*
* @tokens --ui-tooltip-* (@layer variables; read on the content slot)
*/
@layer variables {
:root {
--ui-tooltip-bg: var(--color-foreground);
--ui-tooltip-text: var(--color-background);
--ui-tooltip-border-color: transparent;
--ui-tooltip-rounded: var(--radius-sm);
--ui-tooltip-shadow: var(--shadow-sm);
--ui-tooltip-px: var(--space-xs);
--ui-tooltip-py: var(--spacing);
--ui-tooltip-gap: var(--spacing);
--ui-tooltip-font-family: var(--font-family-mono);
--ui-tooltip-font-size: var(--font-size-xs);
--ui-tooltip-leading: var(--leading-xs);
--ui-tooltip-font-weight: var(--font-weight-body);
--ui-tooltip-max-w: 16ch;
/* Arrow */
--ui-tooltip-arrow-size: calc(var(--spacing) * 2);
--ui-tooltip-arrow-color: var(--ui-tooltip-bg);
--ui-tooltip-arrow-radius: calc(var(--spacing) * 0.5);
/* Reveal transitions: lighter than popover */
--ui-tooltip-transition-duration: calc(var(--default-transition-duration) / 2);
--ui-tooltip-transition-timing-function: var(--default-transition-timing-function);
--ui-tooltip-transition-delay: 0ms;
--ui-tooltip-transition:
opacity var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay),
transform var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay),
overlay var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay) allow-discrete,
display var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay) allow-discrete;
--ui-tooltip-scale--start: 0.94;
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-tooltip-opacity--start: 0;
--ui-tooltip-scale--end: 1;
--ui-tooltip-translate--end: 0, 0, 0;
--ui-tooltip-opacity--end: 1;
/* Positioning: smaller offset than popover menus */
--ui-tooltip-my: var(--spacing);
--ui-tooltip-mx: 0;
}
}
/* Popover overrides, on the tooltip content (declared on :root they would retheme
every popover in the kit, since tooltip.css loads after popover.css). */
@layer ui.components {
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] {
--ui-popover-shadow: var(--shadow-xs);
--ui-popover-min-w: 0; /* dual-mode: no minimum (fit-content cannot pass the typed pair) */
--ui-popover-position-area: block-start span-all;
--ui-popover-origin: bottom center;
}
}
@layer reset {
/* ===========================================================================
HINT POPOVER OVERRIDES: popover.css targets menus; hints need a compact
surface without min-inline-size or backdrop blur.
=========================================================================== */
:where(ui-tooltip, [data-ui~="tooltip"]) :where([data-tooltip-slot~="content"]) {
color: var(--_text-resolved, var(--ui-tooltip-text));
background-color: var(--_bg-resolved, var(--ui-tooltip-bg));
min-inline-size: unset;
--_max-w-len: var(--_max-w-resolved, var(--ui-tooltip-max-w));
--_max-w-num: var(--_max-w-resolved, var(--ui-tooltip-max-w));
max-inline-size: calc(var(--_max-w-len) + var(--_max-w-num) * var(--spacing));
backdrop-filter: none;
transform-origin: var(--ui-popover-origin);
opacity: var(--ui-tooltip-opacity--start);
transform: scale(var(--ui-tooltip-scale--start)) translate3d(var(--ui-tooltip-translate--start));
transition: var(--ui-tooltip-transition);
}
@supports (anchor-name: --zazz) {
:where(ui-tooltip, [data-ui~="tooltip"]) :where([data-tooltip-slot~="content"]) {
--_my-len: var(--_my-resolved, var(--ui-tooltip-my));
--_my-num: var(--_my-resolved, var(--ui-tooltip-my));
margin-block: calc(var(--_my-len) + var(--_my-num) * var(--spacing));
--_mx-len: var(--_mx-resolved, var(--ui-tooltip-mx));
--_mx-num: var(--_mx-resolved, var(--ui-tooltip-mx));
margin-inline: calc(var(--_mx-len) + var(--_mx-num) * var(--spacing));
}
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"]:popover-open {
display: flex;
opacity: var(--ui-tooltip-opacity--end);
transform: scale(var(--ui-tooltip-scale--end)) translate3d(var(--ui-tooltip-translate--end));
@starting-style {
opacity: var(--ui-tooltip-opacity--start);
transform: scale(var(--ui-tooltip-scale--start))
translate3d(var(--ui-tooltip-translate--start));
}
}
}
@layer ui.components {
/* ===========================================================================
TOOLTIP: interestfor sets the ARIA (aria-describedby / aria-details), so do
not add role="tooltip". Explicit anchor-name is needed because the polyfill
does not provide implicit anchors.
=========================================================================== */
:where(ui-tooltip, [data-ui~="tooltip"]) {
anchor-scope: --ui-tooltip-trigger;
display: inline-block;
}
:where(ui-tooltip, [data-ui~="tooltip"]) > :where([interestfor], [data-tooltip-slot~="trigger"]) {
anchor-name: --ui-tooltip-trigger;
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="trigger"] {
display: inline-flex;
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] {
position-anchor: --ui-tooltip-trigger;
display: flex;
align-items: center;
--_gap-len: var(--_gap-resolved, var(--ui-tooltip-gap));
--_gap-num: var(--_gap-resolved, var(--ui-tooltip-gap));
gap: calc(var(--_gap-len) + var(--_gap-num) * var(--spacing));
inline-size: max-content;
--_px-len: var(--_px-resolved, var(--ui-tooltip-px));
--_px-num: var(--_px-resolved, var(--ui-tooltip-px));
padding-inline: calc(var(--_px-len) + var(--_px-num) * var(--spacing));
--_py-len: var(--_py-resolved, var(--ui-tooltip-py));
--_py-num: var(--_py-resolved, var(--ui-tooltip-py));
padding-block: calc(var(--_py-len) + var(--_py-num) * var(--spacing));
border: 1px solid var(--_border-color-resolved, var(--ui-tooltip-border-color));
border-radius: var(--_rounded-resolved, var(--ui-tooltip-rounded));
box-shadow: var(--_shadow-resolved, var(--ui-tooltip-shadow));
font-family: var(--_font-family-resolved, var(--ui-tooltip-font-family));
font-size: var(--_font-size-resolved, var(--ui-tooltip-font-size));
line-height: var(--_leading-resolved, var(--ui-tooltip-leading));
font-weight: var(--_font-weight-resolved, var(--ui-tooltip-font-weight));
text-wrap: balance;
}
/* Descendant (not child) so keys wrapped in a ui-kbd-group still retint */
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] :where(kbd) {
--ui-kbd-bg: color-mix(in oklch, var(--ui-tooltip-text) 15%, transparent);
--ui-kbd-text: var(--ui-tooltip-text);
--ui-kbd-min-w: calc(var(--spacing) * 4);
--ui-kbd-min-h: calc(var(--spacing) * 4);
--ui-kbd-font-size: var(--font-size-xs);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"]:has([data-tooltip-slot~="arrow"]) {
overflow: visible;
}
/* Positioning: reuse --ui-popover-position-* hooks from popover.css via data-popover-side */
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-popover-position-area: block-start span-all;
--ui-popover-origin: bottom center;
--ui-tooltip-my: var(--spacing);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-popover-position-area: block-end span-all;
--ui-popover-origin: top center;
--ui-tooltip-my: var(--spacing);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0.25em, 0, 0;
--ui-popover-position-area: inline-start span-all;
--ui-popover-origin: center right;
--ui-tooltip-mx: var(--spacing);
--ui-tooltip-my: 0;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0.25em, 0, 0;
--ui-popover-position-area: inline-end span-all;
--ui-popover-origin: center left;
--ui-tooltip-mx: var(--spacing);
--ui-tooltip-my: 0;
}
/* Tooltip arrow */
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="arrow"] {
position: absolute;
/* physical top/left/right/bottom throughout the arrow rules: they pair with
the physical [data-popover-side] placement semantics from anchor positioning */
top: calc(100% - var(--ui-tooltip-arrow-size) / 2);
left: 50%;
transform: translate(-50%, 0%) rotate(45deg);
inline-size: var(--ui-tooltip-arrow-size);
block-size: var(--ui-tooltip-arrow-size);
background-color: var(--ui-tooltip-arrow-color);
border-radius: var(--ui-tooltip-arrow-radius);
pointer-events: none;
}
/* Tooltip arrow position */
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"]
[data-tooltip-slot~="arrow"] {
bottom: calc((var(--ui-tooltip-arrow-size) / 2) * -1);
left: 50%;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"]
[data-tooltip-slot~="arrow"] {
top: calc((var(--ui-tooltip-arrow-size) / 2) * -1);
left: 50%;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"]
[data-tooltip-slot~="arrow"] {
left: calc(100% - var(--ui-tooltip-arrow-size) / 2);
top: 50%;
transform: translate(0%, -50%) rotate(45deg);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"]
[data-tooltip-slot~="arrow"] {
right: calc(100% - var(--ui-tooltip-arrow-size) / 2);
left: auto;
top: 50%;
transform: translate(0%, -50%) rotate(45deg);
}
}Disabled
This action is unavailable
<ui-tooltip>
<button
type="button"
data-ui="button"
aria-disabled="true"
interestfor="tooltip-example-disabled-1"
>
Disabled
</button>
<div data-tooltip-slot="content" id="tooltip-example-disabled-1" popover="hint">
This action is unavailable
<span data-tooltip-slot="arrow" aria-hidden="true"></span>
</div>
</ui-tooltip>/**
* tooltip.css: Tooltip (ui-tooltip | [data-ui~="tooltip"]; slots data-tooltip-slot="trigger | content | arrow")
*
* @layer variables, reset, components
* @requires _layers.css, _variables.css, _properties.css (--_*-resolved), popover.css, kbd.css (--ui-kbd-*)
* @uses interestfor: Interest Invokers API (Chromium 142+; polyfill via invokers)
* @uses data-tooltip-slot="content": hint popover (light dismiss, auto-hides sibling hints)
* @uses anchor-name, position-anchor, position-area, position-try-fallbacks:
* explicit anchors for polyfill; data-popover-side maps to --ui-popover-position-*
* @tokens --ui-tooltip-* (@layer variables; read on the content slot)
*/
@layer variables {
:root {
--ui-tooltip-bg: var(--color-foreground);
--ui-tooltip-text: var(--color-background);
--ui-tooltip-border-color: transparent;
--ui-tooltip-rounded: var(--radius-sm);
--ui-tooltip-shadow: var(--shadow-sm);
--ui-tooltip-px: var(--space-xs);
--ui-tooltip-py: var(--spacing);
--ui-tooltip-gap: var(--spacing);
--ui-tooltip-font-family: var(--font-family-mono);
--ui-tooltip-font-size: var(--font-size-xs);
--ui-tooltip-leading: var(--leading-xs);
--ui-tooltip-font-weight: var(--font-weight-body);
--ui-tooltip-max-w: 16ch;
/* Arrow */
--ui-tooltip-arrow-size: calc(var(--spacing) * 2);
--ui-tooltip-arrow-color: var(--ui-tooltip-bg);
--ui-tooltip-arrow-radius: calc(var(--spacing) * 0.5);
/* Reveal transitions: lighter than popover */
--ui-tooltip-transition-duration: calc(var(--default-transition-duration) / 2);
--ui-tooltip-transition-timing-function: var(--default-transition-timing-function);
--ui-tooltip-transition-delay: 0ms;
--ui-tooltip-transition:
opacity var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay),
transform var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay),
overlay var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay) allow-discrete,
display var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function)
var(--ui-tooltip-transition-delay) allow-discrete;
--ui-tooltip-scale--start: 0.94;
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-tooltip-opacity--start: 0;
--ui-tooltip-scale--end: 1;
--ui-tooltip-translate--end: 0, 0, 0;
--ui-tooltip-opacity--end: 1;
/* Positioning: smaller offset than popover menus */
--ui-tooltip-my: var(--spacing);
--ui-tooltip-mx: 0;
}
}
/* Popover overrides, on the tooltip content (declared on :root they would retheme
every popover in the kit, since tooltip.css loads after popover.css). */
@layer ui.components {
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] {
--ui-popover-shadow: var(--shadow-xs);
--ui-popover-min-w: 0; /* dual-mode: no minimum (fit-content cannot pass the typed pair) */
--ui-popover-position-area: block-start span-all;
--ui-popover-origin: bottom center;
}
}
@layer reset {
/* ===========================================================================
HINT POPOVER OVERRIDES: popover.css targets menus; hints need a compact
surface without min-inline-size or backdrop blur.
=========================================================================== */
:where(ui-tooltip, [data-ui~="tooltip"]) :where([data-tooltip-slot~="content"]) {
color: var(--_text-resolved, var(--ui-tooltip-text));
background-color: var(--_bg-resolved, var(--ui-tooltip-bg));
min-inline-size: unset;
--_max-w-len: var(--_max-w-resolved, var(--ui-tooltip-max-w));
--_max-w-num: var(--_max-w-resolved, var(--ui-tooltip-max-w));
max-inline-size: calc(var(--_max-w-len) + var(--_max-w-num) * var(--spacing));
backdrop-filter: none;
transform-origin: var(--ui-popover-origin);
opacity: var(--ui-tooltip-opacity--start);
transform: scale(var(--ui-tooltip-scale--start)) translate3d(var(--ui-tooltip-translate--start));
transition: var(--ui-tooltip-transition);
}
@supports (anchor-name: --zazz) {
:where(ui-tooltip, [data-ui~="tooltip"]) :where([data-tooltip-slot~="content"]) {
--_my-len: var(--_my-resolved, var(--ui-tooltip-my));
--_my-num: var(--_my-resolved, var(--ui-tooltip-my));
margin-block: calc(var(--_my-len) + var(--_my-num) * var(--spacing));
--_mx-len: var(--_mx-resolved, var(--ui-tooltip-mx));
--_mx-num: var(--_mx-resolved, var(--ui-tooltip-mx));
margin-inline: calc(var(--_mx-len) + var(--_mx-num) * var(--spacing));
}
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"]:popover-open {
display: flex;
opacity: var(--ui-tooltip-opacity--end);
transform: scale(var(--ui-tooltip-scale--end)) translate3d(var(--ui-tooltip-translate--end));
@starting-style {
opacity: var(--ui-tooltip-opacity--start);
transform: scale(var(--ui-tooltip-scale--start))
translate3d(var(--ui-tooltip-translate--start));
}
}
}
@layer ui.components {
/* ===========================================================================
TOOLTIP: interestfor sets the ARIA (aria-describedby / aria-details), so do
not add role="tooltip". Explicit anchor-name is needed because the polyfill
does not provide implicit anchors.
=========================================================================== */
:where(ui-tooltip, [data-ui~="tooltip"]) {
anchor-scope: --ui-tooltip-trigger;
display: inline-block;
}
:where(ui-tooltip, [data-ui~="tooltip"]) > :where([interestfor], [data-tooltip-slot~="trigger"]) {
anchor-name: --ui-tooltip-trigger;
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="trigger"] {
display: inline-flex;
}
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] {
position-anchor: --ui-tooltip-trigger;
display: flex;
align-items: center;
--_gap-len: var(--_gap-resolved, var(--ui-tooltip-gap));
--_gap-num: var(--_gap-resolved, var(--ui-tooltip-gap));
gap: calc(var(--_gap-len) + var(--_gap-num) * var(--spacing));
inline-size: max-content;
--_px-len: var(--_px-resolved, var(--ui-tooltip-px));
--_px-num: var(--_px-resolved, var(--ui-tooltip-px));
padding-inline: calc(var(--_px-len) + var(--_px-num) * var(--spacing));
--_py-len: var(--_py-resolved, var(--ui-tooltip-py));
--_py-num: var(--_py-resolved, var(--ui-tooltip-py));
padding-block: calc(var(--_py-len) + var(--_py-num) * var(--spacing));
border: 1px solid var(--_border-color-resolved, var(--ui-tooltip-border-color));
border-radius: var(--_rounded-resolved, var(--ui-tooltip-rounded));
box-shadow: var(--_shadow-resolved, var(--ui-tooltip-shadow));
font-family: var(--_font-family-resolved, var(--ui-tooltip-font-family));
font-size: var(--_font-size-resolved, var(--ui-tooltip-font-size));
line-height: var(--_leading-resolved, var(--ui-tooltip-leading));
font-weight: var(--_font-weight-resolved, var(--ui-tooltip-font-weight));
text-wrap: balance;
}
/* Descendant (not child) so keys wrapped in a ui-kbd-group still retint */
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="content"] :where(kbd) {
--ui-kbd-bg: color-mix(in oklch, var(--ui-tooltip-text) 15%, transparent);
--ui-kbd-text: var(--ui-tooltip-text);
--ui-kbd-min-w: calc(var(--spacing) * 4);
--ui-kbd-min-h: calc(var(--spacing) * 4);
--ui-kbd-font-size: var(--font-size-xs);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"]:has([data-tooltip-slot~="arrow"]) {
overflow: visible;
}
/* Positioning: reuse --ui-popover-position-* hooks from popover.css via data-popover-side */
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-popover-position-area: block-start span-all;
--ui-popover-origin: bottom center;
--ui-tooltip-my: var(--spacing);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0, 0.25em, 0;
--ui-popover-position-area: block-end span-all;
--ui-popover-origin: top center;
--ui-tooltip-my: var(--spacing);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0.25em, 0, 0;
--ui-popover-position-area: inline-start span-all;
--ui-popover-origin: center right;
--ui-tooltip-mx: var(--spacing);
--ui-tooltip-my: 0;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"]:not([data-popover-align]),
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"][data-popover-align="center"] {
--ui-tooltip-translate--start: 0.25em, 0, 0;
--ui-popover-position-area: inline-end span-all;
--ui-popover-origin: center left;
--ui-tooltip-mx: var(--spacing);
--ui-tooltip-my: 0;
}
/* Tooltip arrow */
:where(ui-tooltip, [data-ui~="tooltip"]) [data-tooltip-slot~="arrow"] {
position: absolute;
/* physical top/left/right/bottom throughout the arrow rules: they pair with
the physical [data-popover-side] placement semantics from anchor positioning */
top: calc(100% - var(--ui-tooltip-arrow-size) / 2);
left: 50%;
transform: translate(-50%, 0%) rotate(45deg);
inline-size: var(--ui-tooltip-arrow-size);
block-size: var(--ui-tooltip-arrow-size);
background-color: var(--ui-tooltip-arrow-color);
border-radius: var(--ui-tooltip-arrow-radius);
pointer-events: none;
}
/* Tooltip arrow position */
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="top"]
[data-tooltip-slot~="arrow"] {
bottom: calc((var(--ui-tooltip-arrow-size) / 2) * -1);
left: 50%;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="bottom"]
[data-tooltip-slot~="arrow"] {
top: calc((var(--ui-tooltip-arrow-size) / 2) * -1);
left: 50%;
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="left"]
[data-tooltip-slot~="arrow"] {
left: calc(100% - var(--ui-tooltip-arrow-size) / 2);
top: 50%;
transform: translate(0%, -50%) rotate(45deg);
}
:where(ui-tooltip, [data-ui~="tooltip"])
[data-tooltip-slot~="content"][data-popover-side="right"]
[data-tooltip-slot~="arrow"] {
right: calc(100% - var(--ui-tooltip-arrow-size) / 2);
left: auto;
top: 50%;
transform: translate(0%, -50%) rotate(45deg);
}
}Attributes
| Attribute | Kind | Values | Notes |
|---|---|---|---|
data-ui="tooltip" | Identity | free value | Identity token in data-ui. |
<ui-tooltip> | Tag form | free value | Tag form; styled and, when scripted, registered under this name. |
data-tooltip-slot | Slot | arrow content trigger | Slot: a part inside the primitive. |
Hooks
Set a hook on :root, on a subtree, or inline on one element. A state hook (--…--hover) themes that state only; the primitive keeps its other states.
| Hook | Default | State |
|---|---|---|
--ui-tooltip-arrow-color | var(--ui-tooltip-bg) | — |
--ui-tooltip-arrow-radius | calc(var(--spacing) * 0.5) | — |
--ui-tooltip-arrow-size | calc(var(--spacing) * 2) | — |
--ui-tooltip-bg | var(--color-foreground) | — |
--ui-tooltip-border-color | transparent | — |
--ui-tooltip-font-family | var(--font-family-mono) | — |
--ui-tooltip-font-size | var(--font-size-xs) | — |
--ui-tooltip-font-weight | var(--font-weight-body) | — |
--ui-tooltip-gap | var(--spacing) | — |
--ui-tooltip-leading | var(--leading-xs) | — |
--ui-tooltip-max-w | 16ch | — |
--ui-tooltip-mx | 0 | — |
--ui-tooltip-my | var(--spacing) | — |
--ui-tooltip-opacity--end | 1 | end |
--ui-tooltip-opacity--start | 0 | start |
--ui-tooltip-px | var(--space-xs) | — |
--ui-tooltip-py | var(--spacing) | — |
--ui-tooltip-rounded | var(--radius-sm) | — |
--ui-tooltip-scale--end | 1 | end |
--ui-tooltip-scale--start | 0.94 | start |
--ui-tooltip-shadow | var(--shadow-sm) | — |
--ui-tooltip-text | var(--color-background) | — |
--ui-tooltip-transition | opacity var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function) var(--ui-tooltip-transition-delay), transform var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function) var(--ui-tooltip-transition-delay), overlay var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function) var(--ui-tooltip-transition-delay) allow-discrete, display var(--ui-tooltip-transition-duration) var(--ui-tooltip-transition-timing-function) var(--ui-tooltip-transition-delay) allow-discrete | — |
--ui-tooltip-transition-delay | 0ms | — |
--ui-tooltip-transition-duration | calc(var(--default-transition-duration) / 2) | — |
--ui-tooltip-transition-timing-function | var(--default-transition-timing-function) | — |
--ui-tooltip-translate--end | 0, 0, 0 | end |
--ui-tooltip-translate--start | 0, 0.25em, 0 | start |