Chain Rule Unpacker

Click-through chain-rule walkthrough for y = sin(x²). Two nested coloured boxes (outer = sin, inner = ) split apart in sequence to reveal each layer's local derivative, which then slides down into a product strip that accumulates dy/dx = cos(x²) · 2x.

The component runs as a four-stage narrative — packed, outer-unpacked, inner-unpacked, complete — advanced by a single action button or by a controlling parent via the stage / onStageChange API. The walkthrough is the mental model behind backprop: every layer contributes its local derivative, and the chain rule multiplies them together along the path.

Chain rule unpacker. Stage: packed. y equals sin of x squared. Decomposing derivative into cos of x squared times 2x.y = sin(x²)sin( ... )outerinnercos(x²)2xdy/dx =cos(x²)×2x= dy/dx

This function is a composition — sin wrapped around x². The outer function takes the inner function's output as its input. Unpack the outer layer to find its derivative.

Customize
State
packed
Layout

Installation

npx shadcn@latest add https://craftbits.dev/r/chain-rule-unpacker.json

Usage

import { ChainRuleUnpacker } from "@craft-bits/viz/chain-rule-unpacker";
 
<ChainRuleUnpacker />

Drive the walkthrough from a parent:

const [stage, setStage] = useState(0);
 
<ChainRuleUnpacker stage={stage} onStageChange={setStage} />

Render a custom action button instead of the built-in one:

<ChainRuleUnpacker
  hideAction
  renderAction={({ label, disabled, onClick }) => (
    <MyButton onClick={onClick} disabled={disabled}>{label}</MyButton>
  )}
/>

Hide the narration when embedding inside an article that already explains the chain rule:

<ChainRuleUnpacker hideNarration />

Understanding the component

  1. Composition as nested boxes. Stage 0 renders sin( ... ) as a wide outer box around a smaller inner box. Subtle role hints sit in the corners while the outer stroke breathes on a 3s loop.
  2. Unpacking is a half-split. Each click splits the active box into two halves that slide apart on SPRINGS.smooth, opening just enough room to reveal the local derivative between them.
  3. Derivatives bounce in. Once the box halves are open, the derivative label fades in on SPRINGS.bouncy with a one-shot glow pulse, then a copy slides down into the product strip on SPRINGS.smooth.
  4. Product accumulates left to right. Stage 1 lands cos(x²), stage 2 lands × 2x, stage 3 lands = dy/dx and flashes the strip green to mark completion.
  5. Reset snaps back. Stage 3 → Stage 0 collapses the halves, hides the derivatives, and drains the product strip on SPRINGS.snap so the loop can start again.
  6. Reduced motion. Under prefers-reduced-motion: reduce, every spring is replaced by an instant attribute set — the visual end-state of each stage still renders, but no motion plays.

Props

PropTypeDefaultDescription
stage0 | 1 | 2 | 3Controlled stage. Pair with onStageChange.
defaultStage0 | 1 | 2 | 30Initial stage for the uncontrolled API.
onStageChange(next) => voidFires after the component commits to the next stage.
transitionTransitionSPRINGS.smoothOverride the spring for box splits and label fades.
actionLabelsPartial<Record<stage, string>>Override the action-button label per stage.
narrationCopyPartial<Record<stage, string>>Override the narration copy per stage.
hideNarrationbooleanfalseHide the narration paragraph.
hideActionbooleanfalseHide the built-in action button.
renderAction({ stage, label, disabled, onClick }) => ReactNodeRender slot for a custom action button.
classNamestringMerged onto the root via cn().

Accessibility

  • The root carries aria-labelledby pointing to a hidden <span> summary of the current stage ("Chain rule unpacker. Stage: outer unpacked..."), so screen readers hear the current narrative state without parsing the SVG.
  • The SVG itself is role="img" with the same aria-label, so consumers who slot the SVG into another tree still surface a readable name.
  • An aria-live="assertive" region under the SVG announces each stage transition ("Outer layer unpacked...", "Chain rule complete...") so non-sighted users hear when each new derivative lands.
  • The action button is a real <button type="button"> with :focus-visible outline, disabled state during animation, and standard keyboard activation.
  • Status is never the only signal — every stage emits both an aria-live announcement and a visible label / derivative; colour signals (accent, warning, success) are reinforced by position and copy.
  • Motion respects prefers-reduced-motion: reduce — every spring collapses to an immediate attribute set, including the box-split, derivative reveal, glow pulse, and success flash.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/math/ChainRuleUnpacker.tsx). The library extract drops the project-specific SvgLabel and ChallengeBtn chrome in favour of plain <text> and a cn()-styled <button>, swaps the per-tone --color-accent-* / --color-warn-* / --color-success-* palette for the canonical --cb-accent / --cb-warning / --cb-success token vars, replaces inline spring objects with SPRINGS.smooth / SPRINGS.snap / SPRINGS.bouncy, and adds a controlled stage / onStageChange API plus renderAction slot so consumers can drive the walkthrough from a parent stepper, a keyboard, or a custom button.