Step Grid
step-gridControlsSteps run across the grid, lanes run down, and longer patterns use pages. Tap a cell to toggle it. Drag across a row to paint a stroke on or off. Drag vertically on a lit cell to change its velocity, shown by both fill height and brightness; the second cue keeps low velocities visible in a six-pixel fill. Gate controls the note's length within its step. The host supplies the playhead as an absolute step index, so the cursor follows the audio clock rather than a drifting display timer. Past 16 steps, tabs page through the grid; an opt-in follow mode keeps a running sequence on screen. Without callbacks, the component becomes a read-only mini piano roll. The arpeggiator uses that mode to draw its computed line through the same cells a sequencer edits, with ghost cells for previews that are not pattern truth. Layout and hit testing use the same arithmetic in a pure core, so painted strokes match the rectangles they cover.
Install
Copies the source into your project. Anything it depends on comes with it.
$ pnpm dlx shadcn@latest add @pluginui/core-step-gridPulls in
First time? Add the registry to your components.json once:
{
"registries": {
"@pluginui": "https://pluginui.com/r/{name}.json"
}
}Usage
Connect the documented props to your application or host.
import { StepGrid } from "@/components/ui/step-grid"
// An editable 16-step, 4-lane pattern. The host owns the cells and the clock.
<StepGrid
steps={16}
lanes={4}
cells={cells}
laneLabels={["hat", "snare", "clap", "kick"]}
playhead={currentStep}
onToggle={(step, lane, active) => setCell(step, lane, active)}
onVelocity={(step, lane, velocity) => setVelocity(step, lane, velocity)}
/>
// Read-only: an arpeggiator's computed line as a mini piano-roll.
import { sequenceGrid, stepNoteName } from "maruaudio/ui/shared/step-pattern"
const model = sequenceGrid(heldNotes, { order: "updown", octaves: 2 })
<StepGrid
steps={model.steps}
lanes={model.laneNotes.length}
cells={model.cells.map((c) => ({ step: c.step, lane: c.lane, gate: 0.6 }))}
laneLabels={model.laneNotes.map(stepNoteName)}
playhead={activeStep}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| steps / lanes | number | — | The pattern's full size in steps across all pages, rather than only the visible page. |
| cells | StepGridCellState[] | — | The lit cells, each addressed by absolute step and lane, carrying velocity (0..1), gate (0..1), an optional ghost flag for computed previews, and an optional per-cell color. Everything absent is an empty cell. |
| playhead | number | null | — | Absolute step index the host's clock is on. Drawn as a lit column when it falls on the visible page; null draws no cursor. |
| onToggle | (step, lane, active) => void | — | Report a toggle. Present makes the grid editable; absent makes it a read-only drawing with a summary label. |
| onVelocity | (step, lane, velocity) => void | — | Reports a velocity change from a vertical drag on a lit cell, Shift+arrows on the keyboard, or Shift during a drag for a 4x fine pass. Omit it to make vertical drags paint instead. |
| pageSize | number | 16 | Steps shown at a time. Tabs appear when the pattern outruns one page. |
| page / onPageChange | number / (page) => void | — | Controlled paging. Omit page to let the grid keep its own. |
| followPlayhead | boolean | false | Flip pages to keep the playhead on screen. Off by default so paging around a running sequence does not fight the cursor. |
| groupSize | number | 4 | Columns per beat-group: alternating groups carry a faint tint so beats stay countable. 0 disables. |
| laneLabels | string[] | — | Row captions listed from top to bottom, such as note names or drum names. |
| onInteraction | (event) => void | — | Fires once on the first gesture of any kind. Use it to resume a suspended AudioContext. |
| color | string | — | Override the accent colour for this instance. |
| height | number | 160 | Grid surface height in pixels. |
Interaction
- Drag with pointer capture, so the gesture keeps tracking past the edge of the control.
- Shift while dragging for a fine pass; release it mid-gesture and coarse tracking resumes.
- Double-click resets to
defaultValue. - Arrow keys step normally, Shift+arrow steps finely, PageUp/PageDown jump by ten, Home/End go to the extremes.
- Proper
sliderandswitchroles with livearia-valuetext, so a screen reader announces the formatted value rather than the raw number. - The scroll wheel adjusts the value while the pointer is over the control; stepped ranges move at least one declared step per notch by default.