Skip to content

Tooltip ​

Props ​

PropTypeDefaultOptionsDescription
triggerstring"hover"hover, clickOpen on hover (and on anchor focus when showOnFocus is true) or toggle on click
showOnFocusbooleantrue—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)
subtlebooleanfalse—Subtle color scheme (light bg) vs high-contrast (dark bg)
size"m" | "l""m"m, lCompact (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, leftPreferred tooltip placement (auto-flips if not enough space)
alignment"start" | "center" | "end""center"start, center, endCross-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)
offsetnumber4—Distance (px) between anchor and tooltip
maxWidthstring"320px"—Max width of the tooltip body (CSS value). Use an empty string to remove the cap
disabledbooleanfalse—Prevents the tooltip from showing
openboolean | nullnullnull, true, falseManual override: null = use trigger (hover/focus or click), true = always open, false = always closed. Pair with open-change.
showDelaynumber100—Delay (ms) before showing on hover
hideDelaynumber100—Delay (ms) before hiding after mouse leaves
testSelectorsTestSelectorsPropSee Test SelectorsOverrides stable data-test values.

See Component API — Props.

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.

SizeVariantPadding (block / inline)RadiusTypographyArrow (base × depth)Arrow inset from bubble edge
mDesktop--catnip-space-1 / --catnip-space-2radius-sparagraph-s12 × 6 px6 px
mMobile--catnip-space-2 / --catnip-space-3radius-sparagraph-m12 × 6 px8 px
lDesktop--catnip-space-3 / --catnip-space-4radius-mparagraph-m16 × 8 px12 px
lMobile--catnip-space-3 / --catnip-space-4radius-mparagraph-l16 × 8 px12 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.

  • center centers the bubble on the anchor.
  • start / end align 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 by inset + base / 2 − anchor / 2 so the arrow points at the anchor center. For the default 20px info icon this is 2px for m on desktop, 4px for m on mobile, and 10px for l. 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 ​

EventDetailDescription
open-changebooleanOpen 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 open on the host element from script (for example a template ref and watch) so you assign tooltipRef.value.open = null (or true / false) directly. That path reaches the Vue custom-element setter without the same coercion, so null correctly 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 ​

NameDescription
anchorTrigger (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.
defaultThe tooltip content.

See Component API — Slots.

Methods ​

MethodDescription
reposition()Manually recalculate tooltip position. Call after dynamic content changes (for example anchor resizes).
vue
<template>
  <catnip-tooltip ref="tooltipRef">…</catnip-tooltip>
</template>
<script setup>
const tooltipRef = ref(null);
onMounted(() => {
  // After layout change
  tooltipRef.value?.reposition?.();
});
</script>

Test Selectors ​

AttributeDefault Value
rootcatnip-tooltip
anchorcatnip-tooltip-anchor
contentcatnip-tooltip-content

See Component API — Test selectors.

Catnip Design System by Signicat