Skip to content

feat(grid): add Grid layout primitive#308

Draft
itsprade wants to merge 1 commit into
mainfrom
feat/grid-component
Draft

feat(grid): add Grid layout primitive#308
itsprade wants to merge 1 commit into
mainfrom
feat/grid-component

Conversation

@itsprade

@itsprade itsprade commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Grid — a generic, presentational CSS-Grid layout primitive for arranging arbitrary children (cards, KPIs, fields, dashboards) into equal or custom-width columns, with responsive reflow, gap control, auto-fit sizing, and optional Grid.Item spanning.

Planning ticket: tailor-inc/platform-planning#779

Grid fills a real gap: Layout is a page scaffold (header + fixed-width left/main/right), but there was no generic content grid — developers and AI agents fell back to raw astw:grid astw:grid-cols-*, which is inconsistent and leaks styling internals.

Grid vs. Layout

They're complementary, not overlapping:

Layout (exists) Grid (this PR)
Purpose Page scaffold: header + sidebars + main Generic content grid
Columns Role-based (left 320px / main 1fr / right 280px) Arbitrary equal or custom widths
Children Layout.Header, Layout.Column only Any children (or Grid.Item)
Use case One per page (top-level structure) Many per page (within any column/card)

Design decision

After surveying design systems, grids split into two families: A (span-per-item, 12/24-col — Ant, MUI v2, Mantine Grid) and B (declarative container — Radix Themes, Chakra/Mantine SimpleGrid).

This implements Family B, modeled on Radix Themes Grid, plus Chakra's minChildWidth auto-fit and an optional Grid.Item for spanning. Rationale — it's the most legible model for both AI agents and developers: explicit, strongly-typed props (columns={4}) instead of className soup, no hidden "must sum to 12" invariant, with escape hatches (className, raw template strings).

API

<Grid columns={4} gap={4}></Grid>                         // equal columns
<Grid columns="280px 1fr" gap={6}></Grid>                 // custom widths
<Grid minChildWidth={240} gap={4}></Grid>                 // auto-fit, no breakpoints
<Grid columns={{ initial: 1, md: 2, xl: 4 }} gap={4}></Grid>  // responsive reflow

<Grid columns={4} gap={4}>
  <Grid.Item colSpan={2}>Wide</Grid.Item>
  <Card.Root>Normal</Card.Root>
  <Grid.Item colSpan="full">Footer row</Grid.Item>
</Grid>

Grid props

Prop Type Notes
columns number | string | Responsive<number | string> number → equal columns; string → verbatim grid-template-columns; responsive object
rows number | string | Responsive<number | string> same shape, for rows
gap / gapX / gapY number | Responsive<number> spacing-scale units (41rem)
minChildWidth number | string auto-fit minmax(w, 1fr); overrides columns
flow "row" | "column" | "dense" | "row-dense" | "column-dense" grid-auto-flow
align "start" | "center" | "end" | "stretch" | "baseline" align-items
justify "start" | "center" | "end" | "between" | "around" | "evenly" justify-content
className, style, ...divProps escape hatches + native passthrough

Grid.Item props (optional cell — only to span or place)

Prop Type Notes
colSpan number | "full" | Responsive<…> grid-column: span n (or 1 / -1)
rowSpan number | "full" | Responsive<…> grid-row: span n
colStart / colEnd number | Responsive<number> explicit line placement
className, style, ...divProps

Plain children occupy a single cell; Grid.Item is only needed for spanning/placement. Responsive breakpoints align with Tailwind: initial, sm, md, lg, xl, 2xl.

Implementation notes

  • No new styling infrastructure. Dynamic templates use the CSS-variable + arbitrary-value utility technique already shipped by Layout (--layout-colsastw:grid-cols-[var(--layout-cols)]). Class strings are written as literals so Tailwind compiles them into the shipped stylesheet — verified present in dist/app-shell.css for all breakpoint variants.
  • Directory-based compound (grid/): Grid.tsx (the two components), styles.ts (CSS-var/class resolvers + maps), types.ts, index.ts, Grid.test.tsx — matching description-card/.
  • Public exports: Grid (with Grid.Item attached) + GridProps / GridItemProps only.
  • Self-contained: astw: prefix throughout, data-slot="grid" / data-slot="grid-item", cn(), no globals.css changes, no "use client", fully domain-agnostic.

Docs & demo

  • docs/components/grid.md — full page matching the app-shell doc pattern (Import, Usage, Props tables, responsive/sizing/spanning/alignment, examples, a Grid vs. Layout callout, and notes on the justify / flow gotchas).
  • Interactive demo at /custom-page/grid-demo (nextjs-app) — showcase sections (responsive MetricCard KPIs, auto-fit gallery, custom widths, Grid.Item spanning), a static "Understanding flow" side-by-side illustration, and an interactive playground with sliders for the numeric props and live generated-JSX output.

Tests

  • 20 unit tests (snapshots for each visual mode + behavioral assertions on CSS variables, classes, minChildWidth precedence, responsive breakpoints, and Grid.Item spanning).
  • pnpm type-check, pnpm lint, pnpm test, pnpm fmt:check all pass.

How to test

pnpm install
pnpm dev          # nextjs-app → http://localhost:3000
# → Custom Page → Grid Demo  (route /custom-page/grid-demo)

Changeset

minor — adds Grid.


🤖 Generated with Claude Code

Add `Grid` — a generic, presentational CSS-Grid layout primitive for
arranging arbitrary children into equal or custom-width columns, with
responsive reflow, gap control, auto-fit (`minChildWidth`), and optional
`Grid.Item` spanning. Complements `Layout` (page scaffold) by handling
content-level grids.

Dynamic templates use the CSS-variable + arbitrary-value utility technique
already shipped by `Layout` (`--layout-cols` → `grid-cols-[var(...)]`), so no
new styling infrastructure is introduced.

- packages/core: Grid + Grid.Item, style resolvers in styles.ts, 20 tests
- docs: docs/components/grid.md
- examples/nextjs-app: interactive Grid demo wired into the custom module
- changeset: minor

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant