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.jsonUsage
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
- Directional digit-swap. The number is keyed by
valueinside anAnimatePresencewithmode="popLayout". AuseRef-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. - Spring, not duration. The transition uses
SPRINGS.smoothfrom@craft-bits/core/motion— the same preset the rest of the library uses for slide-in / scroll-following motion. - 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. - Hide-zero short-circuit. Setting
hideZeroreturnsnullwhenvalueis zero, so the badge silently disappears for empty counters without the caller having to gate the render themselves. - Live region. The root span is
aria-live="polite"andaria-atomic="true", so assistive tech announces the new number as a single utterance whenever it changes. Passaria-labelfor richer context.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | required | Numeric 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. |
hideZero | boolean | false | When true, renders nothing if value is zero. |
className | string | — | Merged onto the root <span> via cn(). |
Accessibility
- The root span carries
aria-live="polite"andaria-atomic="true"so screen readers announce the new value as a single utterance whenever it changes. Passaria-label(for examplearia-label="3 unread entries") for richer announcement context — without it, assistive tech only hears the raw number. - The directional slide animation uses
transform+opacityonly, both GPU-composited and respected byprefers-reduced-motionvia 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 aSPRING.smoothalias into a tone-driven, size-tiered library primitive onSPRINGS.smoothfrom@craft-bits/core/motion.