Shape Explorer

A calculator-style primitive for tensor shapes. Each dimension is an editable number input; the header reads back the shape tuple, the total element count, and the memory size at the chosen precision. The user types or arrows up/down to change a dim and the readouts update on every keystroke.

Shape (32, 128, 768). 3,145,728 elements. 6.00 MiB at 2 bytes per element.
Shape · (32, 128, 768)rank 3
elements
3,145,728
memory (2 B/elem)
6.00 MiB
Customize
Shape
[32, 128, 768]
Precision
2 (fp16)
Limits
6

Installation

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

Usage

import { ShapeExplorer } from "@craft-bits/core";
 
<ShapeExplorer defaultShape={[32, 128, 768]} />;

Wire to outside state — useful when a parent lesson needs to react to the shape:

const [shape, setShape] = useState<readonly number[]>([2, 3, 4]);
 
<ShapeExplorer shape={shape} onShapeChange={setShape} />;

Switch precision to fp32 / int8:

// fp32: 4 bytes per element
<ShapeExplorer defaultShape={[32, 128, 768]} bytesPerElement={4} />
 
// int8: 1 byte per element
<ShapeExplorer defaultShape={[32, 128, 768]} bytesPerElement={1} />

Cap the rank to a smaller value:

<ShapeExplorer defaultShape={[8, 16]} maxDims={3} />

Understanding the component

  1. One editable input per dimension. shape is a flat number[]; the component renders an input box per entry, labelled dim 0, dim 1, …. Each edit is parsed, clamped to [minDim, maxDim], and committed via onShapeChange (or internal state in uncontrolled mode).
  2. The readouts are derived. elements = product(shape); memory = elements * bytesPerElement. Both update on every keystroke; the memory readout uses binary prefixes (KiB, MiB, GiB) because tensor allocations align to pages and most ML docs quote binary.
  3. Rank is editable too. The + / buttons append / pop a trailing dimension. New dims seed to 1 so the product (and therefore the memory) doesn't jump when you add a slot. The + button disables at maxDims, the button disables at rank 1.
  4. Controlled or uncontrolled. Pass shape + onShapeChange to lift state, or leave them undefined and the component manages its own value (seeded from defaultShape).
  5. Clamping is invisible. Every dim is forced into [minDim, maxDim] on commit. Type 0, -3, or 99999 and the input snaps to the bound — element-count / memory readouts never display nonsense.
  6. Reduced-motion fallback. With prefers-reduced-motion: reduce, the readout fade transitions collapse to instant.

Props

PropTypeDefaultDescription
shapereadonly number[]Controlled shape. Pair with onShapeChange.
defaultShapereadonly number[][2, 3, 4]Uncontrolled initial shape.
onShapeChange(shape: readonly number[]) => voidFires on every dim edit or add / remove.
bytesPerElementnumber2Bytes per tensor element. 2 ≈ fp16/bf16; 4 ≈ fp32; 1 ≈ int8.
maxDimsnumber6Hard cap on rank. The + button disables when reached.
minDimnumber1Lower clamp applied to each dim on edit.
maxDimnumber8192Upper clamp applied to each dim on edit.
transitionTransitionSPRINGS.smoothSpring used for the readout fade.
classNamestringMerged onto the root via cn().

Accessibility

  • The root is role="figure" with aria-labelledby pointing at the shape heading (Shape · (32, 128, 768)) and aria-describedby at a visually-hidden aria-live="polite" summary.
  • The summary announces the shape, element count, memory size, and bytes-per-element whenever a dim is edited — screen-reader users hear the recalculated total without watching the readout.
  • Every dimension input has an aria-label (Dimension 0 of 3) and a visible monospace label. inputMode="numeric" surfaces the digit keypad on touch devices.
  • Native min / max / step attributes mean arrow keys step by 1 and out-of-range values snap to the bounds on commit.
  • + / buttons carry aria-labels and visibly disable when the rank cap is reached; the disabled state is also conveyed by reduced opacity and a cursor-not-allowed cursor — colour is never the only signal.
  • All focusable controls render a focus-visible:ring-2 ring-cb-accent outline against the --cb-bg page background.
  • prefers-reduced-motion: reduce collapses the readout fade to instant.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/ShapeExplorer.tsx). The source was a bundled lesson widget — a four-shape preset selector, a four-op selector (+, @, mean(0), mean(1)), broadcast / matmul / reduce shape-arithmetic compute, animated cell grids for tensors A / B / Result, a "Shape mismatch" inline banner, and bespoke colour vars (--color-accent-500, --color-warn-400, --color-success-500). The library extract is the form half of that widget — the editable dimension row plus element-count + memory readout — generalised from the source's fixed-rank-2/3 presets to an arbitrary number[] shape with add / remove controls bounded by maxDims. Op selection, paired-tensor broadcast / matmul rendering, and the result grid stay with the source lesson (see also ShapeTracerViz for the multi-step op pipeline and ReshapeExplorer for the cell-morph animation).