Responsive Grid
Build responsive layouts with the mobile-first CSS grid — twelve columns, six breakpoints, a gutter per breakpoint, and responsive spans and column counts.
Overview
The grid system lays content out on twelve columns with <Grid>
and <GridItem>. It is built on
CSS Grid and follows the
viewport through six breakpoints.
import { Container, Grid, GridItem } from '@chassis-ui/react'
export const Example = () => {
return (
<Container>
<Grid>
<GridItem span={4}>One of three columns</GridItem>
<GridItem span={4}>One of three columns</GridItem>
<GridItem span={4}>One of three columns</GridItem>
</Grid>
</Container>
)
}Three items of four tracks each make three equal columns at every viewport width. The
<Container> around them centers the grid in the page; a grid
doesn't need one to work.
This guide used to describe the flexbox grid of <Row> and <Col>, which @chassis-ui/react
0.4 removes. See Migrating from Row/Col to
move a layout over.
Layout primitives
The grid isn't the only layout tool, and two others cover what it isn't the best fit for:
<Stack>for a single row or column of items, like a button toolbar or the fields of a form.<Flex>for items as wide as their content, and for wrapping, justification, and alignment along one axis.
Reach for <Grid> when items share column tracks: page layouts, card grids, and forms whose fields line up across rows.
How it works
The grid system combines breakpoints, containers, a grid, and its items into one layout model:
-
The grid follows six breakpoints. Breakpoints are
min-widthmedia queries, so a value set for one applies to it and to every wider one:span={{ base: 'full', sm: 4 }}is four tracks fromsmup, until another key overrides it. -
Containers center the page and pad its sides. Use
<Container>for a width per breakpoint,<Container fluid>for the full width, or<Container fluidUntil="md">for a mix. The padding of a container is the page margin, separate from the gutter of the grid. -
A grid has twelve columns.
<Grid>divides its width into equal tracks.columnschanges the count, for the whole grid or from a breakpoint up. -
Items span tracks.
<GridItem span={4}>takes four of the twelve. An item with nospanis one track wide, so an item that should fill the row takesspan="full". -
The gutter follows the breakpoint. With no
gap, the space between items is the gutter of the current breakpoint, 1rem belowmdand 1.5rem from it, in both directions.gapsets another, for the whole grid or from a breakpoint up.
Grid options
The grid adapts across six breakpoints. A layout prop takes one value for every width, or an object with a value per breakpoint, each a key of that object. See Grid options on the CSS site for the width, gutter, and page margin of each.
| Breakpoint | Key |
|---|---|
| Extra small |
|
| Small | sm |
| Medium | md |
| Large | lg |
| Extra large | xl |
| Extra extra large | 2xl |
Equal columns
Columns of the same width need no span on each item: the grid counts them for you, or you give it the count.
As many as children
<Grid fill> gives every child a column of the same width, however many there are. Add min to
wrap the children once a column would get narrower than that.
import { Grid } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<Grid fill>
<div>1 of 2</div>
<div>2 of 2</div>
</Grid>
<Grid fill>
<div>1 of 3</div>
<div>2 of 3</div>
<div>3 of 3</div>
</Grid>
</>
)
}A count per breakpoint
columns sets how many equal columns a grid has, and an object changes the count as the
viewport widens: columns={{ base: 1, sm: 2, lg: 4 }}. Further children wrap to the next row. This is what <Row cols> did.
import { Grid } from '@chassis-ui/react'
export const Example = () => {
return (
<Grid columns={{ base: 2, md: 3, lg: 5 }} gap={{ base: 'sm', lg: 'md' }}>
<div>Column</div>
<div>Column</div>
<div>Column</div>
<div>Column</div>
<div>Column</div>
<div>Column</div>
<div>Column</div>
<div>Column</div>
<div>Column</div>
<div>Column</div>
</Grid>
)
}Spans
On the twelve default columns, span gives each item its own width.
All breakpoints
A span with one value holds from the narrowest viewport to the widest.
import { Grid, GridItem } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<Grid>
<GridItem span={3}>span=3</GridItem>
<GridItem span={3}>span=3</GridItem>
<GridItem span={3}>span=3</GridItem>
<GridItem span={3}>span=3</GridItem>
</Grid>
<Grid>
<GridItem span={8}>span=8</GridItem>
<GridItem span={4}>span=4</GridItem>
</Grid>
</>
)
}Stacked to horizontal
Give the items 'full' as the base of their span and a narrower span from a breakpoint up,
as in span={{ base: 'full', md: 4 }}, and they start out stacked and sit side by side from that
breakpoint. A column count does the same for equal columns: 1 as the base, more from the
breakpoint.
import { Grid, GridItem } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<Grid>
<GridItem span={{ base: 'full', sm: 8 }}>span=full sm:span=8</GridItem>
<GridItem span={{ base: 'full', sm: 4 }}>span=full sm:span=4</GridItem>
</Grid>
<Grid columns={{ base: 1, sm: 3 }}>
<div>sm:columns=3</div>
<div>sm:columns=3</div>
<div>sm:columns=3</div>
</Grid>
</>
)
}Mix and match
Give a span a base narrower than the row and other spans from a breakpoint up, so that items
share a row at every width, in different proportions.
import { Grid, GridItem } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<Grid>
<GridItem span={{ base: 'full', md: 8 }}>span=full md:span=8</GridItem>
<GridItem span={{ base: 6, md: 4 }}>span=6 md:span=4</GridItem>
</Grid>
<Grid>
<GridItem span={{ base: 6, md: 4 }}>span=6 md:span=4</GridItem>
<GridItem span={{ base: 6, md: 4 }}>span=6 md:span=4</GridItem>
<GridItem span={{ base: 6, md: 4 }}>span=6 md:span=4</GridItem>
</Grid>
<Grid>
<GridItem span={6}>span=6</GridItem>
<GridItem span={6}>span=6</GridItem>
</Grid>
</>
)
}Start lines
start puts an item at a column line and leaves the tracks before it empty, which is how you
indent or center an item. Unlike the offset of <Col>, a start line counts from the edge of the
grid, not from the item before. Don't center a grid item with an auto margin: it shrinks the item to
its content.
import { Grid, GridItem } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<Grid>
<GridItem span={4}>span=4</GridItem>
<GridItem span={4} start={9}>
span=4 start=9
</GridItem>
</Grid>
<Grid>
<GridItem span={{ base: 'full', md: 6 }} start={{ md: 4 }}>
span=full md:span=6 md:start=4
</GridItem>
</Grid>
</>
)
}Gaps
The gap is the space between items, set on the grid and not on the items. There are no negative margins, so a grid never overflows its container the way a row with wide gutters did.
Both axes
gap takes a token of the spacing scale and applies it between columns and between rows. An
object changes it from a breakpoint up: gap={{ base: 'sm', lg: 'xl' }}.
import { Grid, GridItem } from '@chassis-ui/react'
export const Example = () => {
return (
<Grid gap={{ base: 'sm', lg: 'xl' }}>
<GridItem span={6}>gap=sm lg:gap=xl</GridItem>
<GridItem span={6}>gap=sm lg:gap=xl</GridItem>
<GridItem span={6}>gap=sm lg:gap=xl</GridItem>
<GridItem span={6}>gap=sm lg:gap=xl</GridItem>
</Grid>
)
}One axis
The .column-gap-{token} and .row-gap-{token} utilities set one axis each, in place of the
gutterX and gutterY of <Row>. row-gap-zero suits items that carry a bottom margin of their
own.
import { Grid, GridItem } from '@chassis-ui/react'
export const Example = () => {
return (
<Grid className="column-gap-xl row-gap-2xs">
<GridItem span={6}>column-gap-xl row-gap-2xs</GridItem>
<GridItem span={6}>column-gap-xl row-gap-2xs</GridItem>
<GridItem span={6}>column-gap-xl row-gap-2xs</GridItem>
<GridItem span={6}>column-gap-xl row-gap-2xs</GridItem>
</Grid>
)
}No gap
gap="zero" removes the space on both axes, for items that should touch.
import { Grid, GridItem } from '@chassis-ui/react'
export const Example = () => {
return (
<Grid gap="zero">
<GridItem span={{ base: 'full', sm: 6, md: 8 }}>span=full sm:span=6 md:span=8</GridItem>
<GridItem span={{ base: 6, md: 4 }}>span=6 md:span=4</GridItem>
</Grid>
)
}Content-width columns
A grid sizes its tracks from the container, not from the content. For items as wide as their
content beside items that take the rest of the row, use a <Flex>
with a gap, and .flex-fill on the items that grow.
import { Flex } from '@chassis-ui/react'
export const Example = () => {
return (
<Flex gap="md">
<div className="flex-fill">Fills the row</div>
<div>As wide as its content</div>
<div className="flex-fill">Fills the row</div>
</Flex>
)
}Nested grids
A <Grid> inside an item is a grid of its own, with twelve tracks across the width of the item.
The default gutter follows the viewport, not the width of the grid, so in a nested grid — or one in
a sidebar or a card — it can be too wide for twelve tracks. Give such a grid a gap and a column
count that fits, or let it follow the width of its
query container with contained.
import { Grid, GridItem } from '@chassis-ui/react'
export const Example = () => {
return (
<Grid gap="md">
<GridItem span={{ base: 'full', md: 8 }}>
<Grid columns={2} gap="sm">
<div>Nested column</div>
<div>Nested column</div>
</Grid>
</GridItem>
<GridItem span={{ base: 'full', md: 4 }}>span=full md:span=4</GridItem>
</Grid>
)
}To align nested items with the outer columns instead, make the item a subgrid.
API
Container
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | — |
Render the single child element in place of this component's own element, with this
component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log
in</Link></Button>. The alternative to component for React Server Components — an element
can be passed from the server to this package's client components, a component reference
(component={Link}) can't. | ||
className? | string | — |
| A string of all className you want applied to the base component. | ||
component? | ElementType | — |
| Component used for the root node. Either a string to use a HTML element or a component. | ||
fluid? | boolean | — |
| Set container 100% wide, spanning the entire width of the viewport. | ||
fluidUntil? | Breakpoint | — |
Set container 100% wide until the given breakpoint, after which it scales up with max-width
at every larger breakpoint. | ||
ref? | any | — |
Grid
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | — |
Render the single child element in place of this component's own element, with this
component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log
in</Link></Button>. The alternative to component for React Server Components — an element
can be passed from the server to this package's client components, a component reference
(component={Link}) can't. | ||
className? | string | — |
| A string of all className you want applied to the base component. | ||
columns? | Responsive<number, Breakpoint | ContainerBreakpoint> | — |
Number of columns in the grid template (the column count of @chassis-ui/css when omitted,
12 by default). A count from 1 to 12 is mapped to the grid-cols-{n} class, which applies
to this grid alone. Any other count has no class and is set via the --cx-grid-columns
custom property, which a grid nested in this one inherits: give that grid columns of its
own. Has no effect when fill is set.
An object sets the count from a width up, as in { base: 1, md: 3 }, here and on rows,
gap and flow. A breakpoint key (md) is a width of the viewport. A container key
('@md') is the same width of the nearest query container (an ancestor with the
contains-inline class), and wins over a breakpoint key where both apply. Without a query
container above the grid, a container key never applies. The count of a key is a class, so
a count from 1 to 12. | ||
component? | ElementType | — |
| Component used for the root node. Either a string to use a HTML element or a component. | ||
contained? | boolean | — |
Adds the contained class: the default gutter and column count follow the width of the
nearest query container instead of the viewport, so a grid in a narrow column of a wide page
has the gutter of a narrow page. Without a query container above it the grid keeps the
values of the viewport. columns and gap still override them. | ||
fill? | boolean | — |
Renders .grid-fill instead of .grid — columns expand equally to fill the available
width, with the column count determined by the number of children rather than columns. | ||
flow? | Responsive<GridFlow, Breakpoint | ContainerBreakpoint> | — |
The direction items are placed in, mapped to the grid-flow-* classes: by 'row' (the CSS
default) or by 'column', which fills the rows of one column before it starts the next.
'dense', 'row-dense' and 'column-dense' also move later items into gaps that earlier,
wider ones left. An object sets the flow from a width up. | ||
gap? | { Spacing | string | { base?: Spacing | string } & Partial<Record<Breakpoint | ContainerBreakpoint, Spacing>> } | — |
Gap between grid items: a Spacing token (mapped to the gap-{token} class) or any raw CSS
gap value, including a "{row} {column}" pair (set via the --cx-grid-gap custom
property). The gutter of the current breakpoint when omitted, or of the query container when
contained is set. An object sets the gap from a width up: only a token fits a key other
than base, since a raw value has no class. | ||
min? | string | — |
Minimum column width of a fill grid, set via the --cx-grid-min custom property: any CSS
length, e.g. "16rem". Children wrap to a new row when they would get narrower. 12rem in
@chassis-ui/css when omitted. Only relevant when fill is set. | ||
ref? | any | — |
rows? | Responsive<number, Breakpoint | ContainerBreakpoint> | — |
Number of equal rows in the grid template. A grid has none when omitted: its rows are as
tall as their content. A count from 1 to 6 is mapped to the grid-rows-{n} class; any other
count has no class and is set inline, as the grid-template-rows the class declares. Has no
effect when fill is set. An object sets the count from a width up, where it is a class, so
a count from 1 to 6. | ||
GridItem
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | — |
Render the single child element in place of this component's own element, with this
component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log
in</Link></Button>. The alternative to component for React Server Components — an element
can be passed from the server to this package's client components, a component reference
(component={Link}) can't. | ||
className? | string | — |
| A string of all className you want applied to the base component. | ||
component? | ElementType | — |
| Component used for the root node. Either a string to use a HTML element or a component. | ||
end? | { Responsive<number | 'auto', Breakpoint | ContainerBreakpoint> } | — |
Grid column line to end this item at, mapped to the col-end-{n} class (1 to 13, the line
after the last of 12 columns). With span, it places the item against the end edge of the
grid: span={3} end={13}. 'auto' (col-end-auto) undoes an end line at a wider
breakpoint: { base: 13, md: 'auto' }. | ||
gap? | { Spacing | string } | — |
Gap between the subgrid's children: a Spacing token (mapped to the gap-{token} class) or
any raw CSS gap value (set via the --cx-grid-gap custom property). Only relevant when
subgrid is set. A subgrid has the gap of its own: when the parent <Grid> takes a gap
token, give the subgrid the same one, or its children are narrower than the parent's tracks. | ||
ref? | any | — |
rowEnd? | { Responsive<number | 'auto', Breakpoint | ContainerBreakpoint> } | — |
Grid row line to end this item at, mapped to the row-end-{n} class (1 to 7, the line after
the last of 6 rows), or 'auto' (row-end-auto) to undo an end line at a wider breakpoint.
Takes an object per width, as span does. | ||
rows? | number | — |
Number of equal rows in the subgrid's own row template: the grid-rows-{n} class for a
count from 1 to 6, the same grid-template-rows set inline for any other. Only relevant
when subgrid is set — subgrid only inherits the parent's column tracks, not its rows. | ||
rowSpan? | Responsive<number, Breakpoint | ContainerBreakpoint> | — |
Number of grid row tracks this item spans, mapped to the row-span-{n} class. Takes an
object per width, as span does. | ||
rowStart? | { Responsive<number | 'auto', Breakpoint | ContainerBreakpoint> } | — |
Grid row line to start this item at, mapped to the row-start-{n} class (1 to 6), or
'auto' (row-start-auto) to return it to the flow. Takes an object per width, as span
does. | ||
span? | { Responsive<number | 'full', Breakpoint | ContainerBreakpoint> } | — |
Number of grid column tracks (of the parent <Grid>'s columns) this item spans, mapped to
the col-span-{n} class, or 'full' for every track of the row (col-span-full). An item
with no span is one track wide.
An object sets the span from a width up, as in { base: 'full', md: 6 }, here and on
start, end, rowSpan, rowStart and rowEnd. A breakpoint key (md) is a width of
the viewport. A container key ('@md') is the same width of the nearest query container
(an ancestor with the contains-inline class), and wins over a breakpoint key where both
apply. Without a query container above the grid, a container key never applies. | ||
start? | { Responsive<number | 'auto', Breakpoint | ContainerBreakpoint> } | — |
Grid column line to start this item at, mapped to the col-start-{n} class (1 to 12).
'auto' (col-start-auto) returns the item to the flow, to undo a start line at a wider
breakpoint: { base: 4, md: 'auto' }. | ||
subgrid? | boolean | — |
Turns this item into a nested subgrid: adds .grid/.grid-cols-subgrid alongside its
col-span-{n}/col-start-{n} placement classes, so its own children inherit the parent
<Grid>'s column tracks instead of defining new ones. Combine with span — a subgrid
item must itself be a grid item of the parent for grid-template-columns: subgrid to take
effect, which is why subgrid lives on <GridItem> (the element actually placed as a grid
item) rather than on a <Grid> nested inside it. | ||