Tile Grid Viz

A teaching visualisation for tensor tiling — the pattern at the heart of blocked GEMM, tiled attention, and any hierarchical decomposition that breaks a large matrix into block-sized chunks that fit in a fast memory tier. The component renders an N × M cell grid with thin strokes between cells inside the same tile and thick strokes between tiles. A cursor walks (row, col) row-major over the tile grid: the active tile glows accent, tiles already visited inherit a soft accent tint, and tiles ahead sit plain.

Tile 1 of 16: tile row 1, tile column 1. Tile size 2 × 2.
Tile gridtile 01 / 16
0,00,10,20,31,01,11,21,32,02,12,22,33,03,13,23,3
Customize
Matrix
8
8
Tile
2
2
Playback
600

Installation

npx shadcn@latest add https://craftbits.dev/r/tile-grid-viz.json

Usage

import { TileGridViz } from "@craft-bits/core";
 
<TileGridViz totalRows={8} totalCols={8} tileRows={2} tileCols={2} />

Drive the cursor from outside the component:

const [tile, setTile] = useState({ row: 0, col: 0 });
 
<TileGridViz
  totalRows={8}
  totalCols={8}
  tileRows={2}
  tileCols={2}
  currentTile={tile}
  onCurrentTileChange={setTile}
  playing={false}
/>

Slow the sweep down for an explainer, and hide the (row, col) labels:

<TileGridViz
  totalRows={12}
  totalCols={12}
  tileRows={3}
  tileCols={3}
  playSpeed={1200}
  showTileLabels={false}
/>

Understanding the component

  1. Cells inside, tiles outside. Every cell of the underlying totalRows × totalCols matrix is drawn; thin half-opacity strokes separate cells in the same tile, full-weight strokes separate tiles. The thick borders are the contract the component teaches — they are where a kernel's data boundaries land.
  2. Row-major sweep. Autoplay advances (row, col) in row-major order and wraps back to the origin once every tile has been visited. The scrubber exposes the same flat index.
  3. Three tile states. The current tile fills with --cb-accent and lifts its border to accent weight; past tiles inherit a soft accent tint whose opacity ramps with their distance from the cursor; future tiles sit in --cb-border-muted at ~8% opacity.
  4. Non-square tiles work. tileRows ≠ tileCols is supported; tiles on the right and bottom edges are smaller if totalRows / tileRows doesn't divide evenly. The cursor still treats the tile grid as a uniform (tileRowsCount × tileColsCount) lattice.
  5. SPRINGS.smooth for tile transitions. Fill and border-weight transitions animate via a smooth spring from @craft-bits/core/motion. prefers-reduced-motion: reduce parks the cursor on the final tile, pauses autoplay, and collapses every transition to an instant swap.
  6. Pure primitive. The matrix has no numeric values — this is a layout primitive. Pair it with Attention Heatmap if you want real per-cell weights, or with Flash Attention Viz for the full Q / Kᵀ / O tiling story.

Props

PropTypeDefaultDescription
totalRowsnumber8Total rows in the underlying matrix — clamped to 1..64.
totalColsnumber8Total columns in the underlying matrix — clamped to 1..64.
tileRowsnumber2Rows per tile — clamped to 1..totalRows.
tileColsnumber2Columns per tile — clamped to 1..totalCols.
currentTileTileGridCoordControlled active tile. Pair with onCurrentTileChange.
defaultCurrentTileTileGridCoord{ row: 0, col: 0 }Uncontrolled initial tile.
onCurrentTileChange(tile) => voidFires on autoplay tick and manual scrub.
playingbooleanControlled play state. Pair with onPlayingChange.
defaultPlayingbooleantrueUncontrolled initial play state.
onPlayingChange(playing) => voidFires when play / pause flips.
playSpeednumber600Milliseconds between tile advances.
showTileLabelsbooleantrueDraw (row, col) labels at the center of every tile. Auto-hides on tiles too small to read.
transitionTransitionSPRINGS.smoothSpring for fill + border transitions.
classNamestringMerged onto the root via cn().

Accessibility

  • The root is role="figure" with aria-labelledby pointing at the "Tile grid" heading and aria-describedby at a visually-hidden aria-live="polite" summary.
  • The summary announces the current tile index and (row, col) whenever the cursor advances.
  • The play / pause button uses aria-pressed; the label flips between "Play tile sweep" and "Pause tile sweep".
  • The scrubber is a native <input type="range"> with an explicit aria-label, so keyboard arrows step the cursor and screen readers narrate the value.
  • Colour is never the only signal — every tile carries a (row, col) label (when room allows) and the live summary is textual.
  • prefers-reduced-motion: reduce parks the cursor on the final tile, pauses autoplay, and suppresses tile transitions.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/nn/TileGridViz.tsx). Stripped the lesson-specific phase machine, the SRAM-fit narration, the dual N / B slider panel, the ChallengeBtn dependency, the breathing pulse, the active-tile glow filter, the row/column hover crosshair, and the max 32 × 32 rendering cap. Generalised the fixed SEQ_LENGTHS / BLOCK_SIZES choice arrays to arbitrary totalRows / totalCols / tileRows / tileCols and replaced the timed scan animation with a clean row-major setInterval autoplay matching the FlashAttentionViz cursor model.