Accumulator Dial
A compact semicircular gauge that visualises an accumulating quantity against a target ceiling. The arc fills and the needle sweeps from left (0) to right (target). When previousValue is supplied and differs from value, the dial fires a one-shot delta beat (a subtle scale pop on the needle) so the eye lands on the dial whose accumulator just changed.
Generic over the underlying quantity: works for an Adagrad cache, optimizer step counter, gradient magnitude, budget consumption, capacity meter, or any "how full is this?" reading.
Customize
Shape
160 px
State
80
0
Playback
700 ms
Installation
npx shadcn@latest add https://craftbits.dev/r/accumulator-dial.jsonUsage
import { AccumulatorDial } from "@craft-bits/viz/accumulator-dial";
<AccumulatorDial value={42} target={80} />Drive a per-render delta beat from a parent that already tracks history:
<AccumulatorDial value={current} target={limit} previousValue={prior} />Stack a short label above the dial:
<AccumulatorDial value={0.42} target={1} label="budget" size={160} />Understanding the component
- Self-contained SVG. The dial renders as a viewBox-sized
<svg>element with no positional dependencies. The caller decides where it sits in the layout. - Fraction-driven geometry. The arc fill and needle endpoint are derived from the fraction value over target. Negative values clamp to zero; values above the target clamp to the full sweep.
- Track + fill. A muted full-sweep track sits beneath the accent-coloured arc fill, so the dial reads as "how much of the ceiling is consumed" even at small fractions.
- Delta beat. When
previousValuediffers fromvalue(or the internally tracked last-rendered value differs from the newvalue), the needle group fires a one-shot scale pop, capped at 1.04 to honour thesubtle-deformation-scaleceiling. - Value swap. The center digit rides
AnimatePresence— the new readout fades in from below as the old one slides out, so rapid ticks feel responsive without flicker. - Endpoint labels.
0sits under the left endpoint, the target value sits under the right. Both render incb-fg-subtleso they read as ambient axis labels rather than data. - Reduced motion. Under
prefers-reduced-motion: reduce, the arc transition, value swap, and delta beat all collapse to instant. The digits still update; only the motion drops.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | required | Current accumulator reading. |
target | number | required | Ceiling for the gauge — rightmost needle position. |
previousValue | number | — | Prior reading. When set and different from value, fires a delta beat. |
label | ReactNode | — | Optional short label rendered above the dial. |
size | number | 140 | Diameter in pixels. Floored at 80. |
ariaLabel | string | — | Override the announced string. Defaults to the canonical "Accumulator: value of target" form. |
transition | Transition | SPRINGS.smooth | Arc / value-swap / delta-beat transition. Reduced-motion users snap regardless. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The outer
<svg>isrole="img"with a<title>derived fromariaLabelor the canonical "Accumulator: value of target" string. Screen readers hear both the current value and the ceiling without parsing the SVG geometry. - The dial exposes
data-cb-viz="accumulator-dial"on the root so consumer apps can hook custom styles or assistive tooling. - Value is never the only signal — the center digit is always visible, the arc fills with a colour-independent length, and the endpoint labels remain rendered.
- Motion respects
prefers-reduced-motion: reduce— the arc transition, value swap, and delta beat all collapse to instant. The text content still updates.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/AccumulatorDial.tsx). The source was a lesson-specific Adagrad explainer that hardwired the gradient stream, the step counter, the phase-based narration palette, and the lesson chrome. The craft-bits version strips the simulation and the lesson chrome, accepts avalue/target/previousValuetriple so the same dial covers any accumulating quantity, and reduces the API to the minimum surface needed to plug it into an arbitrary parent.