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, gradient 2(p − t). Pull grows with distance.
  • mae: absolute loss, gradient sign(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.json

Usage

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

  1. 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 of dL/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.
  2. Three loss flavours, one shape. lossType swaps how the gradient is computed without rebuilding the DOM. mse quadruples its pull when the error doubles; mae keeps a constant pull and a sharp turn at the target; huber is mse near the target and mae far from it. The spring stroke and arrow respond in real time so the difference is felt, not just described.
  3. Controlled or uncontrolled prediction. Pass prediction + onPredictionChange to drive from outside (e.g. synced to a scroll step), or leave it uncontrolled and listen to onPredictionChange for side effects.
  4. Keyboard and pointer parity. The prediction marker is role="slider" with arrow-key support (Shift for coarse 0.1 steps, Home / End for the domain ends). The invisible drag target meets WCAG 2.5.8 (≥ 44px) via SVG_TOKENS.hit.minSize.
  5. Relaxed state. When the absolute error drops below 0.005 the spring renders as a tiny relaxed nub, the loss panel adopts the --cb-success tint, and a celebratory bull's-eye ring pulses at the target (skipped under prefers-reduced-motion: reduce).
  6. SPRINGS.smooth on 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

PropTypeDefaultDescription
predictionnumberControlled prediction in [0, 1]. Pair with onPredictionChange.
defaultPredictionnumber0.3Uncontrolled initial prediction in [0, 1].
onPredictionChange(prediction: number) => voidFires on every drag / keyboard step.
targetnumber0.7Target 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.
showGradientbooleantrueRender the −∇L arrow above the prediction marker.
huberDeltanumber0.1Huber transition point — only used when lossType === 'huber'.
transitionTransitionSPRINGS.smoothSpring used for prediction-marker position transitions.
classNamestringMerged onto the root via cn().

Accessibility

  • The figure uses role="figure" with an aria-labelledby heading and an aria-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: reduce snaps 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 accepts mse, mae, or huber. The spring stroke width now derives from SVG_TOKENS.edge tiers (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.