Live Context Indicator
A compact "I'm watching and updating" signal for live-data chrome. A coloured dot — optionally pulsing — paired with an inline label that shimmers each time its value mutates. Pair it with "last updated 2s ago" timestamps, active-stream chips, or any micro-region of the UI that refreshes in place.
Preview
streaming
Customize
Indicator
0
1
Installation
npx shadcn@latest add https://craftbits.dev/r/live-context-indicator.jsonUsage
import { LiveContextIndicator } from "@craft-bits/core";
<LiveContextIndicator status="live" label="streaming" />The label can be any ReactNode. Update its value to fire a shimmer wipe:
<LiveContextIndicator status="live" label="updated 2s ago" />
<LiveContextIndicator status="idle" label="stale 2m" />
<LiveContextIndicator status="paused" label="paused" />Drop the label for a pure status dot — supply aria-label so it announces:
<LiveContextIndicator status="live" aria-label="Live" />Understanding the component
- Status-driven palette.
livepaints a success-coloured dot,idlepaints a warning-coloured dot, andpausedpaints a muted dot. The row usestext-cb-fg-mutedfor the label so the dot carries the signal weight. - Per-status pulse defaults.
liveandidlepulse by default;pauseddoes not. Passpulse={false}to mute a pulsing status, orpulse={true}to force a normally-static status to pulse — useful for emphasising a "checking in…" beat on a paused stream. - Shimmer on label change. Each time the label value changes, the inner span receives a fresh
key, which restarts the gradient wipe. The wipe animatesbackground-positionmasked throughbackground-clip: text— no layout thrash, no DOM mutation beyond the key swap. - Reduced motion respected.
useReducedMotion()short-circuits both the pulse and the shimmer to instant fallbacks. Reduced-motion users see a static coloured dot and a plain label that swaps without animation. - Live-region a11y. The root carries
role="status"andaria-live="polite"so screen readers announce the label every time it updates. The dot isaria-hidden— only the label is announced.
Variants
Live (default pulse)
<LiveContextIndicator status="live" label="streaming" />Idle (slower pulse)
<LiveContextIndicator status="idle" label="stale 2m" />Paused (static)
<LiveContextIndicator status="paused" label="paused" />Dot only
<LiveContextIndicator status="live" aria-label="Live" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
status | 'live' | 'idle' | 'paused' | required | Operational status — drives dot colour and default pulse behaviour. |
label | ReactNode | — | Optional text rendered after the dot. Shimmers each time its value changes. |
pulse | boolean | per-status default | Override the dot pulse. true forces pulsing; false forces static. |
size | 'sm' | 'md' | 'lg' | 'md' | Row size — drives dot diameter, gap, and label scale. |
className | string | — | Merged onto the rendered <div> via cn(). |
...rest | HTMLAttributes<HTMLDivElement> | — | Any other <div> attribute (aria-*, data-*, event handlers, …). |
Accessibility
- The root is announced as a live region:
role="status"plusaria-live="polite". Screen readers read the label each time it updates without interrupting the user. - The dot is
aria-hidden="true"— only the label conveys meaning to assistive tech. When omitting the label, supplyaria-labelso the indicator still has an accessible name. - Pulse and shimmer animations are both suppressed when the user has
prefers-reduced-motion: reduce— the JS layer short-circuits to a static dot and a non-animated label. - All three status colours read from the cb semantic token system (
--cb-success,--cb-warning,--cb-fg-subtle) and clear WCAG AA contrast on the default surface tokens.
Credits
- Extracted from:
algoflashcards(src/platform/ui/LiveContextIndicator.tsx). The original used the project'suseTimershook and inlinebg-success/bg-warningTailwind classes pinned to the old shadcn token system. craft-bits drops the timer hook (the shimmer only needs akeyswap), tokenises every colour, switches tocb-*semantic classes, and replaces inlineease: "easeInOut"strings withEASINGS.inOutfrom@craft-bits/core/motion.