/ component

Grid Builder

Edit grid-template-columns / grid-template-rows (track lists) and grid-template-areas, with two compile-time validators. TrackListLiteral fully validates the track grammar — minmax(1fr, 2fr) → never (an fr is not an inflexible min), repeat(0, 1fr) → never, fit-content / [named-lines] / nested repeat all typed. GridAreasLiteral checks quoting + equal column counts at the type level; the contiguous-rectangle rule is enforced at runtime. Three tiers, a live display:grid preview, and a clickable areas painter.

/ basic-usage

Build a grid template

Controlled value + onChange. Switch between columns, rows, and areas; add tracks or paint cells and the editor emits a valid template string.

repeat(3, 1fr)
/ live-preview

See the template on a real grid

Every edit re-renders a real display: grid node via inline styles. The areas painter is a grid of clickable cells — click to cycle a cell through the area-name palette; the preview places each named area by its bounding rectangle.

track list · grid-template-columns
fn
repeat(3, minmax(80px, 1fr))
preview · columns
1
areas painter · grid-template-areas
3×3rowscols
"head head head" "nav main aside" "foot foot foot"
preview · areas
head
nav
main
aside
foot
preview · columns
1
preview · areas
head
nav
main
aside
foot
/ types

Three usage tiers

From useState-and-go to two strict template-literal validators.

01 casual
string

Just use a string

The escape hatch. value is a plain string — no compile-time checking, drop in any template and go.

1fr 2fr 1fr
const [value, setValue] = useState<string>("1fr 2fr 1fr")
02 intellisense
GridTemplateString

Literal-shaped hints

State typed as GridTemplateString — the union of track-list shapes (repeat(…), minmax(…), sizes, named lines) and quoted areas rows. It is also the onChange return type.

repeat(4, 1fr)
const [value, setValue] = useState<GridTemplateString>("repeat(4, 1fr)")
03 strict
TrackListLiteral<S> · GridAreasLiteral<S>

Two validators at compile time

cssTracks() validates the whole track-list grammar — minmax's min must be inflexible, repeat's count must be a positive int or auto-fill/auto-fit, named lines must be valid idents. cssGridAreas() checks quoting + equal column counts. A violation resolves to never.

repeat(3, minmax(100px, 1fr)) [end]
cssTracks("repeat(3, minmax(100px, 1fr))") // ✓
// @ts-expect-error fr is not an inflexible minmax min
cssTracks("minmax(1fr, 2fr)")
// @ts-expect-error unequal column counts
cssGridAreas("'a a' 'b'")

Note: the contiguous-rectangle rule for areas (each name spans one filled rectangle) is checked at runtime by parseAreas / validateAreasRectangles — it is borderline-undecidable as a template-literal type, so the strict tier validates shape (quoting + equal columns + cell idents). Likewise calc() / var() are undecidable at strict — use the casual / IntelliSense tier for those.

/ api

API

Public surface — component props, runtime helpers, and the type exports.

§ GridBuilder / GridBuilderPanel

<GridBuilder
  value: GridTemplateString | (string & {})
  onChange: (next: GridTemplateString) => void
  mode?: "columns" | "rows" | "areas"
  className?: string
  aria-label?: string
/>

GridBuilder is popover-wrapped; GridBuilderPanel renders the same editor inline. Both are controlled and span all three grid-template properties via the mode tab strip.

PropTypeDescription
valueGridTemplateString | (string & {})Current template value (a track list or a quoted-areas string). Required. `none` is the empty state.
onChange(next: GridTemplateString) => voidFires on every edit. Emits the normalized string. Spans all modes, so it does NOT narrow per property — see GridTemplateStringMap for per-mode shapes.
mode"columns" | "rows" | "areas"Initial active tab + preview target. columns/rows share the track-list grammar; areas is the painter. Default `columns`.

§ Sub-components

<TrackListEditor tokens onChange />

The columns/rows editor — one row per track (size + unit, function text, or [named-line] idents) plus add-size / add-function / add-[line] buttons.

<TrackTokenRow token onChange onRemove />

A single track row, discriminated by token.kind (size | fn | line) with the matching control + a remove button.

<AreasEditor matrix onChange />

The grid-template-areas editor — the painter plus row/column steppers.

<AreasPainter matrix onChange />

A grid of clickable cells. Clicking cycles a cell through the area-name palette + `.` (null cell), writing the new matrix back. Pure inline-style React.

<GridPreview mode columns rows areas />

A real display:grid node (inline styles). columns/rows lay out numbered cells; areas places one cell per named area by its bounding rectangle.

§ Runtime helpers

cssTracks<S>(value: S & TrackListLiteral<S>): S

Call-site track-list validator. Mirrors cssCalc() / cssFilter().

cssGridAreas<S>(value: S & GridAreasLiteral<S>): S

Call-site grid-template-areas validator (shape; rectangle check is runtime).

parseTracks(src: string): TrackToken[] | null

Track list → typed tokens (size | fn | line), or null on a malformed token. `none`/empty → []. Tolerates calc()/var() as opaque fn tokens.

parseAreas(src, { rectangles? }): string[][] | null

Areas string → row-major matrix, or null on bad quoting / unequal columns / bad idents. With { rectangles: true } also enforces contiguous rectangles.

validateAreasRectangles(matrix): boolean

The type-tier punt, at runtime: every named area must form one contiguous rectangle (rejects L-shapes and split blocks).

formatTracks / formatAreas / gridAreaFor / areaNames / defaultTrack

Canonical re-serialization, the grid-area span string for a name, the distinct area names, and a seed token for a fresh track row.

§ Types

TrackListLiteral<S>
Strict track-list validator — S if every token validates (sizes, minmax with inflexible min, fit-content, repeat with recursive tracks, named lines), else never.
GridAreasLiteral<S>
Strict areas validator — S if rows are quoted with an equal cell count and valid cells; rectangle-contiguity is RUNTIME (see scope).
GridTemplateString
Suggestion union: track-list shapes ∪ quoted-areas rows ∪ `none`. The onChange return type.
TrackListString / GridAreasString
The per-property suggestion unions.
GridTemplateStringMap / GridMode
mode → output-string shape, and the mode key union.
TracksOf<S> / TrackCountOf<S>
Tuple of top-level track tokens (named lines excluded), and its length.
AreaRowCountOf<S> / AreaColumnCountOf<S>
Row count and (uniform) column count of an areas string.
GridTemplateState / TrackToken
Exported internal state (discriminated by mode) and the parsed track-token union.

§ Strict-tier scope

  • Track list — full validation. SplitBySpace ParseFunction dispatch: minmax's min is inflexible (a 1fr min is never), repeat's count is a positive int or auto-fill/auto-fit with a recursively- validated track list, fit-content takes a length/percentage, and [idents] are validated.
  • Areas — shape at the type level. Quoting + equal column count + each cell a valid <ident> or null cell (a run of dots).
  • Areas — contiguous-rectangle is a RUNTIME check (the punt). Each area name must span a single filled rectangle. This is borderline-undecidable as a template-literal type and would make tsc crawl, so it lives in parseAreas / validateAreasRectangles. The strict type tier guarantees a parseable shape; the runtime guarantees a valid grid.
  • repeat() nesting is depth-capped and auto-repeat's "no intrinsic/flex track" rule is runtime-only — syntactic repeat(auto-fill, 1fr) type-checks.
  • calc() / var() resolve to never at the strict tier (undecidable). The runtime parser keeps them opaque — use the casual / IntelliSense tier.
/ install

Drop it in

One command via the shadcn CLI.

$ pnpm dlx shadcn@latest add https://turtiesocks.github.io/ridiculous/r/grid-builder.json