Badge

A small inline pill for status, category, or counts. Six tones, an optional leading dot that can pulse for "live" states, and an icon slot for when a dot isn't expressive enough.

defaultlivewarningerrorinfoaccent
Customize
Tone
success
Dot
static

Installation

npx shadcn@latest add https://craftbits.dev/r/badge.json

Usage

import { Badge } from "@craft-bits/core";
 
<Badge tone="success" dot="static">live</Badge>

A pulsing dot is one prop away:

<Badge tone="accent" dot="pulse">streaming</Badge>

Understanding the component

  1. Tone-driven palette. tone selects six semantic color combos (default, success, warning, error, info, accent). The background is a muted surface so the badge sits softly on any page; the foreground borrows the tone color so the eye snaps to the status.
  2. Dot variants. dot="none" (default) renders just the label. dot="static" adds a solid 6px dot before the text. dot="pulse" layers a ring on top that scales outward and fades — the classic "live" indicator.
  3. Pulse via CSS, not JS. The @keyframes cb-badge-pulse rule is injected once into <head> when the first pulsing badge mounts. It's wrapped in @media (prefers-reduced-motion: no-preference) so reduced-motion users see a static dot automatically — no JS branch.
  4. Icon slot. Passing icon replaces the dot. The icon inherits the badge's text color via currentColor, so a lucide-react icon picks up the tone without extra props.
  5. Sized for inline use. align-middle keeps the badge optically centered against surrounding text; whitespace-nowrap prevents long status labels from wrapping mid-flow.

Props

PropTypeDefaultDescription
tone'default' | 'success' | 'warning' | 'error' | 'info' | 'accent''default'Semantic color tone.
dot'none' | 'static' | 'pulse''none'Leading status dot. pulse adds a halo that respects prefers-reduced-motion.
iconReactNodeOptional leading icon. Renders in place of the dot.
childrenReactNoderequiredLabel text.
classNamestringMerged onto the rendered <span>.

Accessibility

  • The badge renders as a single <span> whose visible label is announced as-is by screen readers. Wrap it in an element with role="status" or aria-live="polite" when the value changes dynamically and you want announcements.
  • The dot and any icon are marked aria-hidden="true" so assistive tech reads only the label.
  • The pulse animation is automatically disabled when prefers-reduced-motion: reduce is set — the rule is scoped under @media (prefers-reduced-motion: no-preference). Reduced-motion users see a static dot identical to dot="static".

Credits

  • Extracted from: craftingattention (app/src/components/ui/Badge.tsx).