--primitiv-grid-columns--primitiv-grid-gapA CSS Grid container. columns takes a count or a mobile-first per-breakpoint map, gap the spacing (a density-scaled Context token per step), and align/justify how each item sits within its cell. No inline styles: the per-breakpoint column counts resolve to modifier classes, so the responsive behaviour is correct in server-rendered markup on the first paint.
primitiv add grid installs it whichever mode you are reading.Preview
import { Grid } from "@/components/ui/grid";
<Grid columns={1} gap="md" align="stretch" justify="stretch"> <div>One</div> <div>Two</div> <div>Three</div> {/* ... */}</Grid>Density is set by a data-density ancestor — the Context system, not a Grid prop.
npx primitiv add gridpnpm dlx primitiv add gridyarn dlx primitiv add gridbunx primitiv add gridImport
import { Grid } from "@/components/ui/grid";No headless primitive — this one ships only as a copied styled surface, so primitiv add is the only way in, whichever mode you are reading.
Extends HTMLDivElement — every native attribute of that element is accepted and forwarded.
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | — | Render the single child element instead of a wrapping <div>, merging the
grid classes onto it — e.g. <Grid asChild><ul>...</ul></Grid>. |
| columns | "1" | "2" | "3" | "4" | "5" | "6" | 1 | The column count. The options below are the base tier, applied at every width. Each is also available per breakpoint as primitiv-grid--{tier}-cols-{n}, where tier is one of xs|sm|md|lg|xl|2xl — e.g. primitiv-grid--md-cols-2. Tiers are mobile-first floors, not ranges, and the stylesheet orders them ascending so the widest matching tier wins. A count outside 1–6, or a tier outside this scale, is a media query of your own against --primitiv-grid-columns. |
| gap | "none" | "xs" | "sm" | "md" | "lg" | "xl" | md | Space between tracks; each step (except none) is a density-scaled Context token, mirroring Stack's gap ramp step for step. |
| align | "start" | "center" | "end" | "stretch" | "baseline" | stretch | How each item sits within its cell on the block axis (align-items), as a preset set rather than a raw CSS passthrough. |
| justify | "start" | "center" | "end" | "stretch" | stretch | The inline-axis equivalent (justify-items). Grid distributes tracks itself, so there is no between/around/evenly here the way there is on Stack's flex justify-content. |
--primitiv-grid-columns--primitiv-grid-gap<div> — visual layout only, no semantics. When the items are a list, use asChild to render a <ul> so the count and structure are announced rather than flattened.minmax(0, 1fr), so a cell with long unbreakable content is clipped by its track rather than forcing the whole row wider — which keeps the layout (and horizontal scrolling) predictable for everyone.The reason Grid exists rather than a raw grid-template-columns: columns takes a mobile-first map, one column on the smallest screens escalating to more as space allows. Because each tier is a modifier class, not an inline style, the right column count is in the server-rendered HTML on the first paint — no flash of the wrong layout, no useEffect measuring the viewport.
import { Grid } from "@/components/ui/grid";
<Grid columns={{ base: 1, md: 2, lg: 3 }} gap="lg"> <Card /> <Card /> <Card /></Grid>align (block axis) and justify (inline axis) set how each item sits inside its own cell. Both default to stretch, so items fill the cell — the usual choice for cards. Switch either to start/center/end when the items are smaller than their cells and should pin to an edge, like a row of controls of different sizes lined up along the same baseline.
import { Grid } from "@/components/ui/grid";
<Grid columns={3} gap="md" justify="center" align="center"> <div>One</div> <div>Two</div> <div>Three</div></Grid>