Display
Status Badge
A presence and health indicator — colored dot or tinted pill, with status colors mapped to theme tokens and an optional liveness pulse.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/status-badge.jsonPrompt
The prompt behind this component — paste it into your AI assistant to recreate or adapt it.
Build a React + TypeScript + Tailwind "StatusBadge" component — a presence /
health indicator rendered as a colored dot or a tinted pill.
Contract
- Export a forwardRef span extending React.HTMLAttributes<HTMLSpanElement>.
- Props: status: "online" | "offline" | "busy" | "away" | "custom" (required);
label?: string (visible text — omit for a dot-only badge, screen readers
still get the status name via an sr-only span); pulse?: boolean (defaults
to true when status is "online", false otherwise); variant?: "dot" | "pill"
(default "dot"); customColor?: string (read only when status is "custom" —
pass a theme token like "var(--primary)"; falls back to var(--primary));
className merged via cn(), remaining props + style spread on the root span.
Behavior
- Resolve one CSS color per status from a small lookup table:
online → var(--chart-2), busy → var(--chart-1), away → var(--chart-4),
offline → var(--muted-foreground); "custom" reads customColor.
- The color reaches the DOM only through inline style — never palette
classes — so the mapping stays an editable data table, and the host theme
(including dark mode) decides the actual hues.
- pulse renders an animate-ping clone of the dot behind the solid dot;
the clone is aria-hidden and motion-reduce:hidden — purely decorative,
the state stays fully readable with animations off.
- No label → render an sr-only span with the default status name (Online /
Offline / Busy / Away). The badge is a presentational span; consumers who
need live change announcements add role="status" at the call site.
- Static markup, no hooks / events / browser APIs — do NOT mark it
"use client"; it renders fine as a server component.
Rendering & styling
- dot variant: inline-flex gap-2 text-sm; a size-2 rounded-full dot
(backgroundColor: the resolved color) + label inheriting the surrounding
text color.
- pill variant: rounded-full px-2.5 py-1 text-xs font-medium capsule;
background is color-mix(in oklab, <color> 15%, transparent) and text color
is the full-strength <color> — one variable yields both tint and text, so
contrast holds in light and dark themes for free.
- The dot wrapper is aria-hidden (color is decoration; text carries meaning).
- Semantic tokens only: var(--chart-N), var(--muted-foreground),
var(--primary) — no hex, no palette classes.
Customization levers
- Remap status colors: the four lookup-table entries are the single source of
truth — point online at var(--chart-3) or busy at var(--destructive) and
every badge follows; no call site changes.
- Size: the dot is size-2 — scale to size-2.5 / size-3 together with the
pill's px / py / text-* for a bigger badge; only promote it to a `size`
prop if you need a documented scale.
- Avatar-corner presence recipe: wrap the avatar in a relative container and
drop a label-less badge at the corner:
<span className="relative inline-flex">
<Avatar ... />
<StatusBadge status="online" className="absolute -bottom-0.5 -right-0.5
rounded-full ring-2 ring-background" />
</span>
The sr-only text keeps the state accessible; the ring separates the dot
from the avatar on any surface.
- Pulse policy: pass pulse explicitly to detach it from "online" — pulse a
"deploying" custom status, or force it off everywhere in dense tables.
- New states: add a lookup entry + default label for recurring ones, or use
status="custom" with a per-call-site token.
- Tint strength: the 15% in color-mix is the only knob for pill background
intensity; 10–20% is the useful range.Concepts
- Status-to-token mapping — one lookup table turns a named state into a theme CSS variable; the palette lives in the theme, so a host restyles every badge by editing four entries (or its tokens), never touching call sites.
- Tint from one color —
color-mix(in oklab, <c> 15%, transparent)derives the pill background from the same variable that colors the text, so tint and foreground can never drift apart across light and dark themes. - Pulse as liveness — the ping ring means "live right now", which is why it defaults on only for
online; it is a decorative aria-hidden layer that disappears under reduced motion without losing the state. - Sr-only fallback — a dot-only badge still announces its status name to screen readers; color alone is never the only channel.
- Presentational by default — the badge is a plain span, not a live region; call sites that swap statuses at runtime opt into announcements with
role="status".