registry:ui · NativeHTML
hover card — intent with an interactive panel
The tooltip's intent model (hover delay in, cancellable close grace, focus opens instantly, Escape closes) on a popover=manual panel — but the card is interactive content: pointer and focus CROSSINGS between trigger and card never dismiss; only a real exit or Escape does. No role=tooltip and no aria-describedby — the card is supplementary rich content, not a description.
hover card
Hover the handle, then MOVE ONTO the card — it stays. Click the link inside it. Tab to the trigger: the card opens instantly and stays while focus crosses into it.
shipped by @gaubee
Gaubee — systems
Component grammar, terminal surfaces, the one-brand-hue law.
the component index — the card stays while focus crosses onto this link (the focus-crossing grace).
<HoverCard>
{#snippet trigger()}<a href="#">@gaubee</a>{/snippet}
<div>…rich card…</div>
</HoverCard>types
Hover card variants
Placement and surface paint are the knobs; the delays are hover-card paced by default.
placement
top | bottom | left | right — bottom (under, like a peek) is the convention.
variant
solid | acrylic | auto (default) — the floating-surface paint family.
delays
openDelay=300, closeDelay=200 — tunable per instance.
usage
Usage
The trigger snippet is usually a link; the card is free content — headings, links, images.
<script lang="ts">
import HoverCard from '@ui/hover-card.svelte';
</script>
<HoverCard>
{#snippet trigger()}
<a href="/team/gaubee">@gaubee</a>
{/snippet}
<div class="flex flex-col gap-2">
<p><strong>Gaubee</strong> — systems</p>
<p>component grammar, terminal surfaces</p>
</div>
</HoverCard>a11y
Accessibility
No tooltip role and no aria-describedby — the card is supplementary rich content, not a description.
Keyboard
| Key | Action |
|---|---|
| Tab | Focus opens the card instantly; focus crossings into the card never dismiss |
| Escape | Closes the card immediately (global) |
ARIA
| Attribute | Value | Description |
|---|---|---|
| role | none imposed | Not role=tooltip — compose headings/links inside; the card is content. |
| aria-describedby | — | Deliberately absent: the card is not a description of the trigger. |
| popover | manual | Light dismiss stays OFF — pointer and focus crossings must not dismiss. |
theming
Density and tokens
The panel rides the shared surface-motion kernel and the jx-surface paint law.
hover the trigger — the card measures panel↔anchor live.
hover the trigger — the card measures panel↔anchor live.
hover the trigger — the card measures panel↔anchor live.
hover the trigger — the card measures panel↔anchor live.
| Token | Default | Source |
|---|---|---|
| --jx-hover-{id} | anchor-name | component |
| --jx-p | 0 → 1 timeline | component |
| panel width | max min(88vw, 20rem) | structural |
| open / close delays | 300ms / 200ms | structural |
| --jx-text | 11 / 12 / 13 / 15px | density |
| --jx-inset | 8 / 8 / 12 / 16px | density |
axes
Universal props
The eight-axis surface (explicit-props): size · shape · radius · density · color · theme · elevation · motion — each axis takes named steps, auto (inherit the ambient context; stamps nothing), an exact number (px · coefficient · dp · hue per axis), or query() for responsive/container-conditional values. The peek card carries its OWN elevation — level2 (3dp, the anchored rung): the theme's level table pairs the shadow recipe with the ladder rung; the carriers stamp the promoted panel root (self-carried across the top layer).
HoverCard · universal props
3dp + the surface-container-low rung — the peek card's own rung.
6dp + the surface-container rung.
<HoverCard elevation="level3">{#snippet trigger()}…trigger…{/snippet}…card…</HoverCard>api
API
Eight named props plus the eight universal axes (sixteen interface members; the axes fold below per the convention); trigger and children are the two required snippets.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| id | string | auto | Panel id; also derives the CSS anchor name. |
| children * | Snippet | — | The card content — compose freely (headings, links, images). |
| trigger * | Snippet | — | The trigger content; the wrapper span carries the anchoring. |
| placement | 'top' | 'bottom' | 'left' | 'right' | 'bottom' | Anchored side — bottom (under, like a peek) is the convention. |
| openDelay | number | 300 | Hover delay before the card opens (ms). |
| closeDelay | number | 200 | Close grace spanned across trigger AND panel (ms). |
| variant | 'solid' | 'acrylic' | 'auto' | 'auto' · Own default, not ambient | Floating-surface paint. Defaults: literal slot — own 'auto', ambient when an axis opens. |
| class | string | '' | Forwarded to the trigger wrapper. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |