Reshape Explorer

A teaching primitive for tensor reshape. Render inputShape and outputShape side-by-side as labelled grids; every cell preserves its row-major flat index and morphs from its input position to its output position as progress advances 0 → 1. The element identity stays visible all the way through, so the lesson "reshape changes the indexing, not the data" lands without further narration.

Reshape · [2, 3][3, 2]50%
Reshape from [2, 3] to [3, 2]. Progress 50 percent.
[2, 3]input
[3, 2]output
Customize
Shape
[2,3] → [3,2]
Animation
0.50
Style
36

Installation

npx shadcn@latest add https://craftbits.dev/r/reshape-explorer.json

Usage

import { ReshapeExplorer } from "@craft-bits/core";
 
<ReshapeExplorer inputShape={[2, 3]} outputShape={[3, 2]} />;

Drive the morph from outside — e.g. synced to a scroll scrubber or a slider:

const [progress, setProgress] = useState(0);
 
<ReshapeExplorer
  inputShape={[2, 3]}
  outputShape={[3, 2]}
  progress={progress}
  onProgressChange={setProgress}
/>;

Higher-rank shapes collapse for rendering — the last axis becomes columns, every leading axis is multiplied into rows:

// (2, 3, 4) tensor renders as a 6×4 grid; reshape collapses batch + seq.
<ReshapeExplorer inputShape={[2, 3, 4]} outputShape={[6, 4]} defaultProgress={1} />

Understanding the component

  1. Two panels, one identity. The component renders an input panel and an output panel side-by-side, each showing the target shape as a labelled placeholder. A single layer of morphing cells sits over both panels — one cell per flat index, painted in --cb-accent, labelled with its index when showLabels is on.
  2. Row-major mapping. Cell i sits at (⌊i / inputCols⌋, i mod inputCols) in the input grid and at (⌊i / outputCols⌋, i mod outputCols) in the output grid. As progress advances 0 → 1, each cell's (x, y) is a linear interpolation between those two positions — element identity is preserved, so the eye can track exactly where every value lands.
  3. Higher-rank collapse. inputShape and outputShape accept any rank. Rank 1 renders as a single row; rank ≥ 3 multiplies every leading axis into the row count and keeps the trailing axis as columns. This matches how a higher-rank tensor reads in row-major memory — the most common mental model for reshape.
  4. Shape-compatibility guardrail. When product(inputShape) !== product(outputShape) the component renders an inline shape mismatch banner instead of misrendering. Useful as a teaching primitive — a wrong reshape is supposed to be a loud error.
  5. Controlled or uncontrolled progress. Pass progress + onProgressChange to drive from outside (a slider, a scroll scrubber, a parent stepper), or leave them undefined and use defaultProgress to seed an internal value.
  6. Reduced-motion fallback. prefers-reduced-motion: reduce swaps the SPRINGS.smooth morph for a duration: 0 snap so scrubbing through progress is jank-free.

Props

PropTypeDefaultDescription
inputShapereadonly number[]requiredInput tensor shape. Product must match outputShape.
outputShapereadonly number[]requiredOutput tensor shape. Product must match inputShape.
progressnumberControlled morph phase in [0, 1]. Pair with onProgressChange.
defaultProgressnumber0Uncontrolled initial progress.
onProgressChange(progress: number) => voidFires whenever progress changes.
cellSizenumber36Pixel size of each grid cell.
cellGapnumber4Pixel gap between adjacent cells.
showLabelsbooleantrueRender the per-cell flat-index label.
transitionTransitionSPRINGS.smoothSpring used for the cell-position morph.
classNamestringMerged onto the root via cn().

Accessibility

  • The figure is role="figure" with an aria-labelledby heading that announces both shapes (Reshape · [2, 3] → [3, 2]) and an aria-live="polite" summary that reports the current progress percent.
  • When product(inputShape) !== product(outputShape) the component renders a visible role="alert" mismatch banner; screen readers announce the per-shape element counts inline.
  • The morph layer is marked aria-hidden because its information is redundant with the textual summary — non-sighted users get the shape transition described, not pantomimed.
  • prefers-reduced-motion: reduce replaces the SPRINGS.smooth cell morph with an instant snap so scrubbing through progress never triggers continuous animation for users who opt out.
  • Color is never the only signal: every cell is labelled with its row-major flat index (toggleable via showLabels), and both panels carry textual shape captions.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/ReshapeExplorer.tsx). The source was a multi-mode lesson widget bundled with the Widget chrome, the TogglePill transpose toggle, a four-shape selector (2×3 / 3×2 / 6×1 / 1×6), the bespoke "batch danger" mode with BATCH_COLORS and (2,3,4) → (6,4) flattening, the flat-memory readout strip, and bespoke captions per mode. The library extract is the morph primitive — arbitrary inputShape → outputShape with controlled / uncontrolled progress, a shape-compatibility guardrail, and reduced-motion support. The transpose / batch / readout modes belong to the source lesson and stay there.