Depth Decay Slider

A draggable layer-count slider paired with a signal bar that shrinks as depth grows. The gradient magnitude is computed as decay^n (default 0.5^n); the bar width is a mild blend of linear and sqrt so mid-range depths stay legible while the exponential collapse at deep depths still feels visceral. The learner watches the bar practically vanish, making the vanishing-gradient problem felt rather than computed.

0.5² = 0.250
2 layers. Gradient: 0.250. Signal healthy.
2 layers. Gradient = 0.250 (25.0%). Still strong enough for every layer to learn.
Customize
Range
2
20
State
2
0.50
Layout

Installation

npx shadcn@latest add https://craftbits.dev/r/depth-decay-slider.json

Usage

Uncontrolled — the component owns its own layer count:

import { DepthDecaySlider } from "@craft-bits/viz/depth-decay-slider";
 
<DepthDecaySlider defaultLayers={2} />

Controlled — pair layers with onLayersChange:

const [layers, setLayers] = useState(2);
 
<DepthDecaySlider layers={layers} onLayersChange={setLayers} />

Override the decay constant (e.g. a higher-floor activation) or the depth range:

<DepthDecaySlider minLayers={1} maxLayers={40} decay={0.7} />

Suppress the narration block:

<DepthDecaySlider renderNarration={null} />

Understanding the component

  1. Decay model. Each layer multiplies the gradient by decay. After n layers the magnitude is decay^n. With the default decay = 0.5, two layers leaves a healthy 25%, ten layers leaves about 0.1%, twenty layers leaves a number you cannot train against.
  2. Bar width. A pure exponential collapses too fast to read. The bar width blends the linear fraction and its square root 30 / 70 so the 4–8 layer range stays visible, but the exponential decay still wins by layer 10+.
  3. Phase colouring. Layer counts fall into one of four phases (healthy, weakening, vanishing, dead) driven by phaseCutoffs. Each phase maps to a semantic CSS-var so the colour follows the user's theme.
  4. Layer stack. Below the bar, one small box renders per layer with a small multiplication label inside (hidden once there are too many boxes to fit). The stack reinforces the multiplicative model — every box is one multiplication step.
  5. Drag plus keyboard. The transparent hit target spans the slider track; pointer events drag the thumb, and arrow keys step by 1 (Shift+Arrow steps by 5). Home jumps to minLayers, End jumps to maxLayers.
  6. Idle pulse. When the slider sits at minLayers, the bar's outline pulses gently to invite interaction. The pulse drops under prefers-reduced-motion.
  7. Reduced motion. The bar tween, glow tween, and mount animation all collapse to instant under prefers-reduced-motion: reduce. The component still updates synchronously — only the spring drops.

Props

PropTypeDefaultDescription
layersnumberCurrent layer count (controlled).
defaultLayersnumberminLayersInitial layer count (uncontrolled).
onLayersChange(next: number) => voidFired when the user drags or arrows the slider.
minLayersnumber2Minimum selectable layer count.
maxLayersnumber20Maximum selectable layer count.
decaynumber0.5Per-layer multiplier. The signal decays as decay^n.
ticksreadonly number[][2, 5, 10, 15, 20]Tick marks rendered under the track.
phaseCutoffsDepthDecaySliderPhaseCutoffs{ healthy: 4, weakening: 8, vanishing: 14 }Cutoffs deciding which phase a layer count falls into.
renderNarration((args) => ReactNode) | nulldefault narrationNarration override. Pass null to suppress the narration block.
transitionTransitionSPRINGS.snapBar-fill animation transition. Reduced-motion users snap regardless.
ariaLabelstringderivedAccessible name override.
classNamestringMerged onto the root via cn().

Accessibility

  • The slider hit target carries role="slider", aria-valuemin / aria-valuemax / aria-valuenow, plus an aria-valuetext that includes the formatted gradient — so screen readers hear both the layer count and the magnitude.
  • Keyboard support covers Arrow / Shift+Arrow / Home / End; focus is visible via the thumb ring (stroke plus radius bump).
  • The SVG is role="img" with a comprehensive aria-label; an additional aria-live="polite" status region announces phase transitions in plain language.
  • Motion respects prefers-reduced-motion: reduce — the bar tween, glow tween, mount animation, and idle pulse all collapse to instant.
  • The root carries data-cb-viz="depth-decay-slider" and data-phase so consumer apps can hook custom styles or assistive tooling.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/math/DepthDecaySlider.tsx). The source hardwired lesson-specific colour vars, a project SvgLabel primitive, and a ca-narration chrome class. The craft-bits version maps the four phases onto semantic cb-* tokens, replaces the project-specific label primitive with bare <text> carrying the canonical cb-* font and size, exposes the decay constant and phase cutoffs as props, and adds proper controlled / uncontrolled state plumbing.