Grid
Equal-column CSS grid with responsive column counts.
import { Grid } from "@ps1ui/core";Columns
<Grid columns={3} gap="md">
<div>01</div>
<div>02</div>
<div>03</div>
<div>04</div>
<div>05</div>
<div>06</div>
</Grid>Responsive columns
<Grid columns={{ base: 1, sm: 2, md: 3 }} gap="md">
<div>01</div>
<div>02</div>
<div>03</div>
<div>04</div>
<div>05</div>
<div>06</div>
</Grid>Column span with GridItem
<Grid columns={4} gap="md">
<GridItem colSpan={2}>hero</GridItem>
<GridItem>02</GridItem>
<GridItem>03</GridItem>
<GridItem>04</GridItem>
<GridItem>05</GridItem>
<GridItem>06</GridItem>
</Grid>Responsive column span
<Grid columns={4} gap="md">
<GridItem colSpan={{ base: 4, md: 2, lg: 4 }}>hero</GridItem>
<GridItem>02</GridItem>
<GridItem>03</GridItem>
<GridItem>04</GridItem>
<GridItem>05</GridItem>
</Grid>Semantic element
Pass as to render a tag other than the default <div>. A card grid is usually a list, so <Grid as="ul"> beats layeringrole="list" onto a <div> — and because a <ul> may only contain <li>, GridItem carries the same prop. Change the two together.
Everything else is unchanged: columns, gap, colSpan and queryContainer behave identically on any tag, the rendered element's own attributes become available, and as also accepts a component rather than a tag name.
As a list
- hero
- 02
- 03
{/* A card grid is a list — say so in the markup rather than
with role="list" on a <div>. Pair the tags: <ul> takes <li> children. */}
<Grid as="ul" columns={3} gap="md" aria-label="cards">
<GridItem as="li" colSpan={2}>hero</GridItem>
<GridItem as="li">02</GridItem>
<GridItem as="li">03</GridItem>
</Grid>Query container
Responsive props resolve against the nearest ancestor that is a container-query context — normally the PS1Root wrapping your app. Pass queryContainer to make aGrid that context instead. This is also what makes a childGridItem's responsive colSpan track the Grid's own width rather than the page's.
It is opt-in because container-type: inline-size erases the element's intrinsic width: an opted-in Grid collapses to zero inside any shrink-to-fit parent (a row Stack, an auto grid track, a float). Give it a definite inline size whenever you turn it on.
Query container
{/* The inner Grid — and any GridItem colSpan inside it —
now measures the 320px outer Grid instead of the page. */}
<Grid queryContainer columns={1} style={{ width: 320 }}>
<Grid columns={{ base: 1, md: 3 }} gap="md">
<div>01</div>
<div>02</div>
<div>03</div>
</Grid>
</Grid>Props
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | "div" | Element or component to render instead of the default <div> — e.g. "ul" for a card grid, paired with `<GridItem as="li">`. |
columns | number | 1 | Number of equal-width columns. |
gap | "none" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "md" | Gap between cells on the space scale. |
queryContainer | boolean | false | Make this Grid a container-query context so descendants' responsive props — including child `GridItem`s' `colSpan` — resolve against its width instead of the nearest ancestor container. Costs the Grid its intrinsic width — it collapses to 0 in shrink-to-fit parents (row flex, auto grid track, float), so give it a definite inline size when opting in. |
Also accepts the attributes of the element rendered via as (default: <div>).
GridItem Props
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | "div" | Element or component to render instead of the default <div> — pair it with the parent Grid's `as` (e.g. `as="li"` inside `<Grid as="ul">`) so the markup stays valid. |
colSpan | number | 1 | Number of grid columns the item spans. |
Also accepts the attributes of the element rendered via as (default: <div>).