Kern GitHub

Tooltip

Supplementary plain-text tooltip for focusable interactive triggers. Requires @alpinejs/anchor.
Requires Alpine.js
Tooltip adds supplementary plain-text help to an existing interactive control. The default slot must contain a focusable interactive element such as a button, link, input, select, or textarea. The component deliberately does not add a `tabindex` fallback: a focusable static element without a role is not a meaningful trigger. Tooltip content is unreachable on touch devices, supplementary only, never the sole location of essential information. The `text` prop is plain text; rich content means edit source. On init, the wrapper finds the first focusable descendant and sets `aria-describedby` to the generated tooltip id so the hidden tooltip remains a resolvable description target. The tooltip stays in the DOM with `x-show` and is positioned with `x-anchor`, so install `@alpinejs/anchor` anywhere you copy this component.

Button trigger

1{{ partial:components/primitives/tooltip text="Archived projects stay searchable." }}
2 <button
3 type="button"
4 class="focus-visible:border-focus-text focus-visible:outline-focus active:border-focus active:bg-focus active:text-focus-text border-foreground hover:bg-muted inline-flex h-10 items-center border-2 px-3 text-sm font-medium focus-visible:outline focus-visible:outline-4"
5 >
6 Archive
7 </button>
8{{ /partial:components/primitives/tooltip }}

Link trigger

View changelog
1{{ partial:components/primitives/tooltip text="Opens the public changelog in this window." }}
2 <a
3 href="#"
4 class="text-primary focus-visible:border-focus-text focus-visible:outline-focus active:border-focus active:bg-focus active:text-focus-text inline-flex border-2 border-transparent px-1 text-sm font-medium underline underline-offset-4 hover:border-current focus-visible:outline focus-visible:outline-4"
5 >
6 View changelog
7 </a>
8{{ /partial:components/primitives/tooltip }}

Wrapper class

1{{ partial:components/primitives/tooltip text="The class prop targets only the inline-block wrapper." class="align-middle" }}
2 <button
3 type="button"
4 class="focus-visible:border-focus-text focus-visible:outline-focus active:border-focus active:bg-focus active:text-focus-text border-foreground hover:bg-muted inline-flex size-10 items-center justify-center border-2 text-sm font-bold focus-visible:outline focus-visible:outline-4"
5 aria-label="More information"
6 >
7 ?
8 </button>
9{{ /partial:components/primitives/tooltip }}

Props

Name Type Default Description
text string Required plain-text tooltip content
class string Additional classes merged via tw_merge (root element only)

Slots

Name Fallback / Default Description
default Trigger content; must contain a focusable interactive element such as a button, link, or input

Source

1{{#
2 @name Tooltip
3 @desc Supplementary plain-text tooltip for focusable interactive triggers. Requires @alpinejs/anchor.
4 @param text string - Required plain-text tooltip content
5 @param class string - Additional classes merged via tw_merge (root element only)
6 @slot default - Trigger content; must contain a focusable interactive element such as a button, link, or input
7#}}
8{{ _class = 'inline-block {class}'
9 | tw_merge }}
10<div
11 class="{{ _class }}"
12 x-data="{
13 open: false,
14 trigger: null,
15 focusableSelector: `button:not([disabled]), a[href], input:not([type=hidden]):not([disabled]), select:not([disabled]), textarea:not([disabled])`
16 }"
17 x-id="['kern-tooltip']"
18 x-init="
19 trigger = $el.querySelector(focusableSelector);
20 if (trigger) trigger.setAttribute('aria-describedby', $id('kern-tooltip'));
21 "
22 @mouseenter="open = true"
23 @mouseleave="open = false"
24 @focusin="open = true"
25 @focusout="open = false"
26 @keydown.escape.window="if (open) open = false"
27>
28 {{ slot }}
29 <div
30 x-show="open"
31 x-anchor.top.offset.8="trigger"
32 :id="$id('kern-tooltip')"
33 role="tooltip"
34 class="bg-foreground text-background z-50 px-2.5 py-1 text-sm"
35 style="display: none"
36 >
37 {{ text | entities }}
38 </div>
39</div>

Dependencies

Packages

1composer require marcorieser/tailwind-merge-statamic
2npm install alpinejs @alpinejs/anchor