Counter Badge
A vertical HUD-style counter for lesson chrome. Three stacked rows — a monospace label on top, a large tabular-nums value in the middle, and a proportional progress bar underneath. When the value reaches maxValue, the number and the fill bar swap to the success tone and the digit gains a soft glow halo; the optional isNewMax flag plays a brighter pop the next time the value changes.
Reach for it when a lesson canvas needs a corner-of-screen counter — sweep-line overlap, DFS stack depth, window element count, frequency-counter peaks — that should feel like a HUD readout, not body copy.
Installation
npx shadcn@latest add https://craftbits.dev/r/counter-badge.jsonUsage
import { CounterBadge } from "@craft-bits/edu";
<CounterBadge value={active} maxValue={peak} label="Active" />Mark the moment the parent records a fresh peak so the next value change pulses the brighter celebration beat:
<CounterBadge
value={active}
maxValue={peak}
label="Active"
isNewMax
/>Drop the progress bar entirely when the counter is part of a denser stat row:
<CounterBadge value={depth} maxValue={maxDepth} label="Depth" hideTrack />Anatomy
- Root —
inline-flex flex-col items-end, spreads unknown props onto a<div>. - Label — small monospace, muted, uppercase, tracking-wider. Pass
nullor empty string to suppress. - Value — large
tabular-nums, coloured bytonewhile climbing and bycb-successat peak. Keyed byvalueso every update re-runs a small scale beat onSPRINGS.bouncy. - Peak glow — a soft
text-shadowhalo usingcolor-mix(in srgb, var(--cb-success), transparent)so the glow adapts to light/dark themes without a hardcoded hex. - Progress bar — animates
scaleX(origin-left) instead ofwidthso the motion is GPU-composited. Hidden byhideTrack. - New-max beat — the badge tracks the previous peak state in a ref; the brighter
1.05scale beat fires once on the transition into peak, not on every render after. - Reduced motion —
prefers-reduced-motion: reduceshort-circuits the entrance scale and the glow halo.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | required | Current counter value. Drives the pop-on-change animation and the progress fill. |
maxValue | number | required | Peak value the counter is climbing toward. Triggers the success glow when value >= maxValue. |
label | ReactNode | 'Active' | Small monospace label above the number. Pass null or empty string to suppress. |
tone | 'default' | 'accent' | 'info' | 'warning' | 'error' | 'default' | Resting tone — at peak the badge always swaps to success. |
isNewMax | boolean | false | Plays a brighter celebratory pop on the next value change. |
hideTrack | boolean | false | Drop the progress bar; keep the label + value. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The value carries
aria-live="polite"andaria-atomic="true"so screen readers announce the new number on every change without interrupting. - The progress track is
aria-hidden— it is a decorative reflection of the value / maxValue ratio, not an independent fact. - Motion is
transform/opacity/filter/text-shadowonly — neverwidth/height/top/left. The fill bar animatesscaleX, notwidth. prefers-reduced-motion: reduceshort-circuits the entrance scale beat and the peak glow halo — the new value appears instantly at scale1with no shadow.- The peak glow uses
color-mix(in srgb, var(--cb-success), transparent)rather than a pure-black shadow, so it stays inside the design-token palette and adapts to dark mode.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/chrome/CounterBadge.tsx). The source paired hardcodedrgba(34, 197, 94, 0.30)glow with project-specificSEMANTIC_HEX['success'], awidthanimation on the progress fill, andaccentHex/accentGlowraw-string overrides. craft-bits' version retones tocb-*semantic tokens, swaps thewidthanimation for a GPU-compositedscaleXtransform, replaces the raw-string overrides with atoneenum, and adds an explicit "new max" celebratory beat that fires only on the transition into peak.