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.
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)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.
Three usage tiers
From useState-and-go to two strict template-literal validators.
Just use a string
The escape hatch. value is a plain string — no compile-time checking, drop in any template and go.
1fr 2fr 1frconst [value, setValue] = useState<string>("1fr 2fr 1fr")
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)")
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
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.
| Prop | Type | Description |
|---|---|---|
| value | GridTemplateString | (string & {}) | Current template value (a track list or a quoted-areas string). Required. `none` is the empty state. |
| onChange | (next: GridTemplateString) => void | Fires 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[][] | nullAreas 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→ParseFunctiondispatch:minmax's min is inflexible (a1frmin isnever),repeat's count is a positive int orauto-fill/auto-fitwith a recursively- validated track list,fit-contenttakes 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
tsccrawl, so it lives inparseAreas/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 — syntacticrepeat(auto-fill, 1fr)type-checks.calc()/var()resolve toneverat the strict tier (undecidable). The runtime parser keeps them opaque — use the casual / IntelliSense tier.
Drop it in
One command via the shadcn CLI.
$ pnpm dlx shadcn@latest add https://turtiesocks.github.io/ridiculous/r/grid-builder.json