Row By Row Animator
A nine-phase walk-through of a 2×2 matrix-vector multiplication, rendered one row at a time. The learner taps Step through phases 0–8: each row of the matrix scans the input vector, the active pair (A[i][j], x[j]) lights up, a product chip pops above the matrix, and once both products in a row exist they converge along an arc into the corresponding output slot. The terminal phase pulses both outputs and reveals y = Ax.
The component owns its own phase machine; Step advances it, and once you reach phase 8 the button becomes Replay and re-arms cleanly.
Row 0 is about to scan the input. Every entry in this row will pair with a matching entry in x — one multiplication each, then a single sum.
Installation
npx shadcn@latest add https://craftbits.dev/r/row-by-row-animator.jsonUsage
import { RowByRowAnimator } from "@craft-bits/viz/row-by-row-animator";
<RowByRowAnimator />Re-skin with a different matrix and vector:
<RowByRowAnimator
matrix={[[0.7, -0.4], [-1.5, 0.6]]}
vector={[2, -1]}
/>Drive a parent reducer off each phase change:
<RowByRowAnimator
onPhaseChange={(phase) => {
/* sync with sibling narration, analytics, etc. */
}}
/>Understanding the component
- Coordinate system. The diagram is a 480 × 200 SVG. The matrix sits on the left, the input vector to its right, and the output column on the far right. Row 0 lives near the top, row 1 near the bottom. Product chips float above the matrix for row 0 and below it for row 1.
- Phase machine. Internal state is the phase index 0 to 8. Each phase has its own choreography (row 0 highlights / pair 0 / pair 1 / row 0 lands / row 1 highlights / pair 2 / pair 3 / row 1 lands / terminal pulse). Replay resets to 0.
- Imperative animation. Every per-frame attribute write lands on a ref via motion's
animate()—strokeDashoffsetfor the connection draws and convergence arcs, opacity for cell glows and chips, scale for the output pop. React only re-renders on phase changes, so the SVG itself never tears. - Pair palette. The first pair in each row uses
--cb-accent, the second uses--cb-warning, and the final convergence arc + output uses--cb-success. The hues stay distinguishable independent of theme because they pull from semantic tokens; consumers can recolour the whole animation by overriding the tokens in their app. - Row band. A faint full-width band highlights whichever row is currently scanning. It animates between rows so the structural "row 1 now takes over" moment reads even if the learner missed a step.
- Reduced motion. Under
prefers-reduced-motion: reduce, every phase collapses to an instant attribute set: connection lines snap to their drawn state, chips appear without slide, the convergence arcs are skipped, and the output cells appear instantly.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
matrix | readonly [readonly [number, number], readonly [number, number]] | [[1, 2], [3, 4]] | 2 × 2 weight matrix. Row 0 drives phases 0–3; row 1 drives phases 4–7. |
vector | readonly [number, number] | [5, 6] | Two-element input vector, multiplied row-by-row. |
transition | Transition | SPRINGS.bouncy | Override the chip / output pop spring. |
onPhaseChange | (phase) => void | — | Fires after each Step (or Replay) with the new phase index. |
onReset | () => void | — | Fires when the user clicks Replay. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The SVG has
role="img"and anaria-labelsummarising the matrix, the vector, and the current phase. - The component root has
aria-labelledbypointed at ansr-onlyheading naming the two matrix rows and the input vector. - A live region below the buttons announces the current phase index and narration prose so screen-reader users get the same narrative as sighted users.
- The narration paragraph also has
aria-live="polite"and reads as plain prose; it is the canonical explanation for each phase. - Colour is never the only signal — every phase change updates the narration text and the live-region status as well as the SVG.
- The Step / Replay button has a visible focus ring via
:focus-visibleand is the only operable control, so the entire animator is reachable in one Tab. - Motion respects
prefers-reduced-motion: reduce— every staged animation collapses to an instant attribute set.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/RowByRowAnimator.tsx). The source was a tightly bundled lesson component — it consumedSvgLabelfor every text run,ChallengeBtnfor the Step button, depended on per-track lesson palette tokens, and inlined anEASINGS.outimport. The viz extract drops the lesson chrome (raw<text>plus a token-styled button), remaps the colour palette tovar(--cb-*)semantic tokens so consumer themes repaint freely, re-keys the pop spring to the canonicalSPRINGS.bouncy, and replaces the project-specific easing import with a literal cubic-bezier so the component carries no project-specific motion import.