Variants
Opinionated HintHotspot presets — badge, beacon-with-label, what-s-new-pill — with WCAG-AA contrast and ≥24×24 px hit targets.
Why Variants?
The default <HintHotspot> dot ships at 12×12 px, which can be hard to spot on busy interfaces. The three named variants below are opinionated presets that solve the most common discovery patterns:
variant="badge"— count-bearing high-contrast circle (notification-style).variant="beacon-with-label"— pulsing dot plus an adjacent label.variant="what-s-new-pill"— sparkle pill that fades after the first interaction.
Each preset renders at ≥24×24 px (above the WCAG 2.5.5 minimum touch target), passes WCAG 2.1 AA contrast on light and dark backgrounds, and respects prefers-reduced-motion: reduce.
All variant props are additive — a consumer that doesn't pass variant still sees the legacy 12×12 dot with byte-identical output to v1.
badge
A 24×24 circular badge with an optional count slot. Values above 99 clamp to "99+".
import { HintHotspot } from '@tour-kit/hints';
<HintHotspot
variant="badge"
count={3}
targetRect={anchor.getBoundingClientRect()}
position="top-right"
/>Props
Prop
Type
beacon-with-label
A pulsing dot adjacent to a short label. The label is aria-hidden because the button already exposes an aria-label, so screen readers don't double-read.
<HintHotspot
variant="beacon-with-label"
label="New"
side="right"
targetRect={anchor.getBoundingClientRect()}
position="top-right"
/>The beacon pulses via motion-safe:animate-tour-pulse. Under prefers-reduced-motion: reduce, the useReducedMotion() JS gate strips the animation entirely (tier 3 of the reduced-motion contract).
Props
Prop
Type
what-s-new-pill
A pill with an inline sparkle icon plus a label. After the first pointerdown or focus, the pill fades to opacity: 0 over 200ms.
<HintHotspot
variant="what-s-new-pill"
label="What's new"
targetRect={anchor.getBoundingClientRect()}
position="top-right"
/>Under prefers-reduced-motion: reduce, the fade is replaced by a hard removal — the component returns null after the first interaction so the element doesn't linger as an invisible-but-focusable target in the tab order.
Props
Prop
Type
Reduced-motion contract
Every variant participates in the three-tier reduced-motion defense:
motion-safe:Tailwind prefix strips animation classes when the OS pref isreduce.@media (prefers-reduced-motion: reduce)wraps every custom@keyframes(e.g.tour-pulse).useReducedMotion()JS gate drives render-time branches — e.g.what-s-new-pillreturnsnull(instead ofopacity: 0) under reduce so the element is fully removed.
Mock the hook in tests:
import { vi } from 'vitest';
vi.mock('@tour-kit/core', async (orig) => ({
...(await orig<typeof import('@tour-kit/core')>()),
useReducedMotion: vi.fn(() => true),
}));Composing the lower-level components
The three variant components are exported for advanced composition:
import { HintBadge, HintBeaconWithLabel, HintWhatsNewPill } from '@tour-kit/hints';The recommended path is the discriminated variant prop on <HintHotspot> — it auto-narrows the variant-specific extras (count, label, side) at the call site.
Related
<HintHotspot>reference — base component these variants extend.@tour-kit/hintsoverview — when to use hints vs. tours vs. announcements.- Headless hint primitives — build a fully custom variant from scratch.
- Reduced motion guide — three-tier defense the variants participate in.
- Hint types reference —
HintConfig,HotspotPosition, and variant prop types.
Ship onboarding, not config.
npm i @tour-kit/core is MIT and free. The Pro packages work unlicensed too — a one-time $99 license removes the production watermark when you ship.
MIT-licensed — no signup, no credit card. Pay once, only when you ship.