Principles.
Six rules shape every primitive. Knowing them makes the API predictable before you read a single prop table.
01
Parents own spacing.
Children never carry margin. The container declares one gap and every child inherits the rhythm. Moving a component to a new parent never drags stray spacing along with it.
// Avoid: spacing lives on each child
<div>
<Card className="mb-4" />
<Card className="mb-4" />
</div>
// Prefer: the Stack owns the rhythm
<Stack gap={4}>
<Card />
<Card />
</Stack>02
One element, no runtime.
Each primitive renders a single element with utility classes. There is no measurement, no context, and nothing to hydrate. What you see in the DOM is what you wrote.
<Row gap={2} justify="between" />
// renders
<div data-slot="row" class="flex min-w-0 flex-row flex-nowrap gap-2 items-center justify-between" />03
A constrained scale.
Gap accepts 0, 1, 2, 3, 4, 6, 8, and 12. Columns accept 1 to 6 and 12. The constraint is the point: it keeps interfaces aligned without a review comment. Every scale can also change per breakpoint.
// lib/spacing.ts — one vocabulary shared by every primitive
type Gap = 0 | 1 | 2 | 3 | 4 | 6 | 8 | 12;
type Align = "start" | "center" | "end" | "stretch" | "baseline";
type Justify = "start" | "center" | "end" | "between" | "around" | "evenly";
type Breakpoint = "base" | "sm" | "md" | "lg" | "xl" | "2xl";
type Responsive<T> = T | Partial<Record<Breakpoint, T>>;
<Stack gap={{ base: 3, md: 6 }} align={{ base: "stretch", lg: "center" }} />04
Native props pass through.
Every primitive extends the props of its element. className merges after the generated classes, so an escape hatch is always one attribute away.
<Grid cols={3} gap={4} className="auto-rows-fr" role="list" aria-label="Plans">
…
</Grid>05
Content never breaks the frame.
Every primitive sets min-w-0, because flex and grid children otherwise refuse to shrink below their content. Long text truncates; tables scroll; the layout holds.
<Row>
<Col grow>
{/* truncate works here because Col sets min-w-0 */}
<span className="truncate">{veryLongTitle}</span>
</Col>
</Row>06
Containers and children have separate jobs.
Stack and Row are containers: they own gap, align, and justify, with exactly the same values on both axes. Col is a child: it decides how one region shares space with grow, shrink, and self. A region that needs its own rhythm nests a Stack inside a Col.
<Row gap={4} align="start">
<Col shrink={false} className="w-44">…</Col>
<Col grow>
<Stack gap={2}>…</Stack>
</Col>
<Col self="center">
<Button>Save</Button>
</Col>
</Row>