Tooltip
Props
| Prop | Type | Default | Options | Description |
|---|---|---|---|---|
| trigger | string | "hover" | hover, click | Open on hover (and on anchor focus when showOnFocus is true) or toggle on click |
| showOnFocus | boolean | true | — | When trigger is hover, keep the tooltip open while the anchor is focused. Set false for hover-only visibility (mouse leave hides it even if the field stays focused) |
| subtle | boolean | false | — | Subtle color scheme (light bg) vs high-contrast (dark bg) |
| size | "m" | "l" | "m" | m, l | Compact (m) or standard (l) padding, border-radius, typography, and arrow dimensions. Each size has a desktop and a mobile variant below 768px (Sizes). s is deprecated and renders as m |
| placement | "top" | "right" | "bottom" | "left" | "top" | top, right, bottom, left | Preferred tooltip placement (auto-flips if not enough space) |
| alignment | "start" | "center" | "end" | "center" | start, center, end | Cross-axis placement of the bubble vs the anchor. start / end keep the bubble flush with the anchor edge unless the anchor is too small for the arrow to reach its center; then the bubble extends past that edge just enough for the arrow to point at the anchor center (Alignment) |
| offset | number | 4 | — | Distance (px) between anchor and tooltip |
| maxWidth | string | "320px" | — | Max width of the tooltip body (CSS value). Use an empty string to remove the cap |
| disabled | boolean | false | — | Prevents the tooltip from showing |
| open | boolean | null | null | null, true, false | Manual override: null = use trigger (hover/focus or click), true = always open, false = always closed. Pair with open-change. |
| showDelay | number | 100 | — | Delay (ms) before showing on hover |
| hideDelay | number | 100 | — | Delay (ms) before hiding after mouse leaves |
| testSelectors | TestSelectorsProp | See Test Selectors | Overrides stable data-test values. |
Sizes
m is the compact tooltip and l the standard one. Each has a desktop variant and a mobile variant; the mobile variant applies below the M breakpoint (max-width: 767px) and is larger, with more padding and a larger typography token. Typography tokens also switch to their mobile values in that band.
| Size | Variant | Padding (block / inline) | Radius | Typography | Arrow (base × depth) | Arrow inset from bubble edge |
|---|---|---|---|---|---|---|
m | Desktop | --catnip-space-1 / --catnip-space-2 | radius-s | paragraph-s | 12 × 6 px | 6 px |
m | Mobile | --catnip-space-2 / --catnip-space-3 | radius-s | paragraph-m | 12 × 6 px | 8 px |
l | Desktop | --catnip-space-3 / --catnip-space-4 | radius-m | paragraph-m | 16 × 8 px | 12 px |
l | Mobile | --catnip-space-3 / --catnip-space-4 | radius-m | paragraph-l | 16 × 8 px | 12 px |
In every size the arrow's point is rounded with a 2 px radius (radius-xs).
Alignment
The arrow always aims at the anchor center and stays at least its inset away from the bubble's corners.
centercenters the bubble on the anchor.start/endalign the bubble's start or end edge with the anchor's. When the anchor is narrower than twice the arrow's reach (inset + half the arrow base), the bubble extends past that edge byinset + base / 2 − anchor / 2so the arrow points at the anchor center. For the default 20px info icon this is 2px formon desktop, 4px formon mobile, and 10px forl. Wider anchors, such as buttons, stay flush.- Viewport clamping takes precedence near screen edges, so the arrow may sit off its usual position there.
Events
| Event | Detail | Description |
|---|---|---|
| open-change | boolean | Open state changed (true / false). Use with controlled open (.open + @open-change). |
See Component API — Events. Unwrap event.detail[0] ?? event.detail in Vue.
Controlling open from Vue
The open prop is intentionally tri-state (null = follow hover/click, true / false = forced open or closed).
If you bind :open="someRef" in a Vue 3 template and toggle someRef from false back to null, the tooltip can stay forced closed even though you intended uncontrolled mode. That behaviour comes from Vue’s runtime DOM patching, not from Catnip: for custom-element hosts, patchDOMProp can coerce null / undefined to false once the host property is already typed as a boolean (for example after false was applied once). This is tracked upstream as vuejs/core#14209. A related change proposal is vuejs/core#14459 — check whether it is merged in the Vue version you ship before assuming reactive :open can express null again.
What works today
- Set
openon the host element from script (for example a templaterefandwatch) so you assigntooltipRef.value.open = null(ortrue/false) directly. That path reaches the Vue custom-element setter without the same coercion, sonullcorrectly restores hover/click behaviour. - Plain attributes (
setAttribute/removeAttribute) on<catnip-tooltip>are fine for HTML-only apps; with automatic type conversion enabled, Catnip avoids stripping those attributes when coercing string values to booleans.
For the broader pattern (any catnip-* prop that needs null on a boolean-typed host property), see Usage — Vue 3: nullish values on custom elements.
Slots
| Name | Description |
|---|---|
| anchor | Trigger (defaults to info icon). With a custom anchor, use a focusable control (<button>, <a href>, etc.); the wrapper does not add tabindex, so focus stays on that control. |
| default | The tooltip content. |
Methods
| Method | Description |
|---|---|
reposition() | Manually recalculate tooltip position. Call after dynamic content changes (for example anchor resizes). |
<template>
<catnip-tooltip ref="tooltipRef">…</catnip-tooltip>
</template>
<script setup>
const tooltipRef = ref(null);
onMounted(() => {
// After layout change
tooltipRef.value?.reposition?.();
});
</script>Test Selectors
| Attribute | Default Value |
|---|---|
root | catnip-tooltip |
anchor | catnip-tooltip-anchor |
content | catnip-tooltip-content |