Count Badge

A small numeric pill suitable for inbox counts, unread chips, queue depths, and any other "current count" indicator. When the value changes, the incoming digit slides in from below (if the value grew) or from above (if it shrank) via SPRINGS.smooth, so changes are felt as motion instead of flickering through hard text swaps.

3
Customize
Tone
accent
Size
md
Hide Zero

Installation

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

Usage

import { CountBadge } from "@craft-bits/core";
 
<CountBadge value={3} aria-label="3 unread entries" />

Pass a tone to retone the pill against any semantic token:

<CountBadge value={12} tone="error" />

Hide the badge automatically when the count drops to zero — useful for inbox chips that should disappear when empty:

<CountBadge value={unread} hideZero />

Understanding the component

  1. Directional digit-swap. The number is keyed by value inside an AnimatePresence with mode="popLayout". A useRef-backed comparison between the new and previous value picks the slide direction: growth → incoming digit rises from below, shrink → incoming digit falls from above. initial={false} on the presence suppresses the first-render animation.
  2. Spring, not duration. The transition uses SPRINGS.smooth from @craft-bits/core/motion — the same preset the rest of the library uses for slide-in / scroll-following motion.
  3. Tone + size via CVA. Six base tones (accent, success, warning, error, info, foreground) drive background and text color. Three sizes (sm, md, lg) scale height, min-width, padding, and font together so the proportions hold from inline-row use to standalone chips.
  4. Hide-zero short-circuit. Setting hideZero returns null when value is zero, so the badge silently disappears for empty counters without the caller having to gate the render themselves.
  5. Live region. The root span is aria-live="polite" and aria-atomic="true", so assistive tech announces the new number as a single utterance whenever it changes. Pass aria-label for richer context.

Props

PropTypeDefaultDescription
valuenumberrequiredNumeric value displayed. Each change triggers a directional digit-swap.
tone'accent' | 'success' | 'warning' | 'error' | 'info' | 'foreground''accent'Semantic color of the pill.
size'sm' | 'md' | 'lg''md'Visual size — drives height, padding, and font.
hideZerobooleanfalseWhen true, renders nothing if value is zero.
classNamestringMerged onto the root <span> via cn().

Accessibility

  • The root span carries aria-live="polite" and aria-atomic="true" so screen readers announce the new value as a single utterance whenever it changes. Pass aria-label (for example aria-label="3 unread entries") for richer announcement context — without it, assistive tech only hears the raw number.
  • The directional slide animation uses transform + opacity only, both GPU-composited and respected by prefers-reduced-motion via the underlying Motion preset (SPRINGS.smooth).
  • Background colors on every tone meet WCAG AA contrast against the paired text token in both light and dark themes; verify with custom theme tokens if you re-skin.

Credits

  • Extracted from: algoflashcards (src/platform/ui/CountBadge.tsx). Generalized from a project-specific inbox chip with a SPRING.smooth alias into a tone-driven, size-tiered library primitive on SPRINGS.smooth from @craft-bits/core/motion.