Error Spring
A physics-style intuition for gradient-based learning. The prediction is a draggable point on a [0, 1] axis; the target is a green diamond; a coiled spring sits between them. The further the prediction is from the target, the fatter the coil and the louder the loss. The warning-coloured arrow above the prediction marker points in the direction of −∇L — the way a gradient step would move it. Drop the prediction on the target and the spring relaxes, the loss panel turns green, and a bull's-eye ring celebrates.
Switch lossType to feel how each one pulls differently:
mse: quadratic loss, gradient2(p − t). Pull grows with distance.mae: absolute loss, gradientsign(p − t). Constant pull, sharp turn at the target.huber: quadratic close, linear far. The robust compromise.
Error spring · MSEp 0.30 → t 0.70
Prediction 0.30, target 0.70. Loss 0.16 (MSE). gradient -0.80.L = (p − t)² = 0.16
Customize
Loss
mse
Target
0.7
Display
Installation
npx shadcn@latest add https://craftbits.dev/r/error-spring.jsonUsage
import { ErrorSpring } from "@craft-bits/core";
<ErrorSpring />Drive the prediction from outside, e.g. synced to a training-step scrubber:
const [prediction, setPrediction] = useState(0.3);
<ErrorSpring
prediction={prediction}
onPredictionChange={setPrediction}
target={0.7}
lossType="mse"
/>Compare loss flavours by swapping a single prop:
<ErrorSpring lossType="mae" />
<ErrorSpring lossType="huber" huberDelta={0.15} />Understanding the component
- Spring as gradient. The coil between the prediction and the target is not just decoration — its stroke width and amplitude grow with
|prediction − target|. The spring's "tension" mirrors the magnitude ofdL/dp; the gradient arrow above the marker shows its direction (−∇L, the way descent would step). Physics-style intuition for why "follow the gradient downhill" reaches the target. - Three loss flavours, one shape.
lossTypeswaps how the gradient is computed without rebuilding the DOM.msequadruples its pull when the error doubles;maekeeps a constant pull and a sharp turn at the target;huberismsenear the target andmaefar from it. The spring stroke and arrow respond in real time so the difference is felt, not just described. - Controlled or uncontrolled prediction. Pass
prediction+onPredictionChangeto drive from outside (e.g. synced to a scroll step), or leave it uncontrolled and listen toonPredictionChangefor side effects. - Keyboard and pointer parity. The prediction marker is
role="slider"with arrow-key support (Shiftfor coarse 0.1 steps,Home/Endfor the domain ends). The invisible drag target meets WCAG 2.5.8 (≥ 44px) viaSVG_TOKENS.hit.minSize. - Relaxed state. When the absolute error drops below
0.005the spring renders as a tiny relaxed nub, the loss panel adopts the--cb-successtint, and a celebratory bull's-eye ring pulses at the target (skipped underprefers-reduced-motion: reduce). SPRINGS.smoothon the marker. Programmatic prediction changes spring the marker along the axis using the smooth motion token from@craft-bits/core/motion. Reduced-motion users see an instant snap.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
prediction | number | — | Controlled prediction in [0, 1]. Pair with onPredictionChange. |
defaultPrediction | number | 0.3 | Uncontrolled initial prediction in [0, 1]. |
onPredictionChange | (prediction: number) => void | — | Fires on every drag / keyboard step. |
target | number | 0.7 | Target value in [0, 1]. Rendered as the green diamond. |
lossType | 'mse' | 'mae' | 'huber' | 'mse' | Loss function used for the readout, spring tension, and gradient arrow. |
showGradient | boolean | true | Render the −∇L arrow above the prediction marker. |
huberDelta | number | 0.1 | Huber transition point — only used when lossType === 'huber'. |
transition | Transition | SPRINGS.smooth | Spring used for prediction-marker position transitions. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The figure uses
role="figure"with anaria-labelledbyheading and anaria-live="polite"summary announcing the prediction, target, loss, and gradient on every change. - The prediction marker is
role="slider"with full keyboard support:ArrowLeft/ArrowRight(fine 0.01),Shift+ arrows (coarse 0.1),Home/End(domain bounds). - The hit target meets WCAG 2.5.8 (
SVG_TOKENS.hit.minSize, 44px). - Color is never the only signal — values, loss equation, and target label are all textual.
prefers-reduced-motion: reducesnaps the marker on changes and skips the bull's-eye celebration ring.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/nn/ErrorSpring.tsx). Stripped the lesson-specific quadratic-loss narration, MSE-only phase thresholds, and the loss-gauge milestone bar — generalised the spring into a loss-agnostic primitive that acceptsmse,mae, orhuber. The spring stroke width now derives fromSVG_TOKENS.edgetiers (so it scales with the rest of the library), and the gradient arrow above the marker is a first-class prop instead of a hard-coded annotation.