Skip to main contentSkip to docs navigation

The layout grid, on native CSS Grid — twelve columns, a gutter per breakpoint, spans and column counts per breakpoint or container width, auto-fill, and subgrid.

Overview

<Grid> renders the .grid of @chassis-ui/css: a display: grid container with twelve columns and, between its items, the gutter of the current breakpoint. <GridItem> places a child on the columns and rows. <Grid> replaces <Row> and <Col>, which version 0.4 removes with the flexbox grid they rendered — see Migrating from Row/Col. The Responsive Grid guide builds page layouts from the pieces documented here. For a single row or column of items, <Stack> and <Flex> are a lighter fit.

A Tailwind build of @chassis-ui/css has the classes of the <Grid> and <GridItem> props only with its safelist loaded: see Tailwind.

Basic usage

Three equal-width columns in a twelve-column grid each span four tracks:

span=4
span=4
span=4
JSX
import { Grid, GridItem } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid>
      <GridItem span={4}>span=4</GridItem>
      <GridItem span={4}>span=4</GridItem>
      <GridItem span={4}>span=4</GridItem>
    </Grid>
  )
}

Item placement

<GridItem> places a child within a <Grid>. An item with no placement props is one track wide and takes the next free cell.

Span

Set span to a number from 1 to 12 to size an item to that many column tracks, rendered as the .col-span-{n} class. An object in place of the number sets the span per breakpoint: base is the span from the narrowest viewport, and a key named after a breakpoint (sm/md/lg/xl/2xl) is the span from that breakpoint up. Every placement prop of <GridItem> takes such an object, as do columns, rows, gap and flow of <Grid>. Three span={{ base: 6, md: 4 }} items span six tracks, and four from the md breakpoint:

span=6 md:span=4
span=6 md:span=4
span=6 md:span=4
JSX
import { Grid, GridItem } from '@chassis-ui/react'

export const Example = () => {
  return (
    <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>
  )
}

span="full" spans every track of the row (.col-span-full), whatever the column count. An item that stacks below a breakpoint needs it as the base: with a span at md only, the item is one track wide below md, not full width.

span=full
span=full md:span=6
span=full md:span=6
JSX
import { Grid, GridItem } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid>
      <GridItem span="full">span=full</GridItem>
      <GridItem span={{ base: 'full', md: 6 }}>span=full md:span=6</GridItem>
      <GridItem span={{ base: 'full', md: 6 }}>span=full md:span=6</GridItem>
    </Grid>
  )
}

Start

Set start to a column line from 1 to 12 to position an item, independent of the items before it (.col-start-{n}). An offset in the flexbox grid was relative to the previous column; a start line is absolute.

span=4 start=3
span=4
JSX
import { Grid, GridItem } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid>
      <GridItem span={4} start={3}>
        span=4 start=3
      </GridItem>
      <GridItem span={4}>span=4</GridItem>
    </Grid>
  )
}

start="auto" returns an item to the flow, to undo a start line from a breakpoint up:

JSX
<GridItem span={6} start={{ base: 4, md: 'auto' }} />

End

Set end to the column line an item ends at, from 1 to 13 (.col-end-{n}): line 13 is the end edge of twelve columns. With span, it places an item against that edge, whatever comes before it.

span=3
span=3 end=13
JSX
import { Grid, GridItem } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid>
      <GridItem span={3}>span=3</GridItem>
      <GridItem span={3} end={13}>
        span=3 end=13
      </GridItem>
    </Grid>
  )
}

end="auto" undoes an end line from a breakpoint up, as start="auto" does a start line.

Rows

Set rows on <Grid> for the number of equal rows in its template: a count from 1 to 6 renders the .grid-rows-{n} class. A grid with no rows has no template rows, and each row is as tall as its content. Place an item on the rows with rowSpan (.row-span-{n}, 1 to 6), rowStart (.row-start-{n}, 1 to 6, or 'auto') and rowEnd (.row-end-{n}, 1 to 7, or 'auto'). All three take a value per breakpoint on an item, as rows does on the grid.

span=4 rowSpan=2
span=8
span=8 start=5 rowStart=2
JSX
import { Grid, GridItem } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid rows={2}>
      <GridItem span={4} rowSpan={2}>
        span=4 rowSpan=2
      </GridItem>
      <GridItem span={8}>span=8</GridItem>
      <GridItem span={8} start={5} rowStart={2}>
        span=8 start=5 rowStart=2
      </GridItem>
    </Grid>
  )
}

Columns and gap

columns sets the number of columns and gap the space between items. A column count from 1 to 12 renders the .grid-cols-{n} class, and a Spacing token in gap (the scale <Flex> and <Stack> use) renders the .gap-{token} class. Any other gap is a raw CSS value — a length, or a "{row} {column}" pair — set through the --cx-grid-gap custom property. With no gap, the grid has the gutter of the breakpoint: 1rem below md and 1.5rem from it.

span=2
span=2
span=6
span=6
span=6
span=6
span=4
span=4
span=4
JSX
import { Grid, GridItem } from '@chassis-ui/react'

export const Example = () => {
  return (
    <>
      <Grid columns={4} gap="1rem">
        <GridItem span={2}>span=2</GridItem>
        <GridItem span={2}>span=2</GridItem>
      </Grid>
      <Grid gap=".25rem 1rem">
        <GridItem span={6}>span=6</GridItem>
        <GridItem span={6}>span=6</GridItem>
        <GridItem span={6}>span=6</GridItem>
        <GridItem span={6}>span=6</GridItem>
      </Grid>
      <Grid gap="lg">
        <GridItem span={4}>span=4</GridItem>
        <GridItem span={4}>span=4</GridItem>
        <GridItem span={4}>span=4</GridItem>
      </Grid>
    </>
  )
}

An object changes columns and gap from a breakpoint up, with the same classes. Only a token fits a breakpoint's gap, since a raw value has no class; base takes either. A breakpoint's columns is a count from 1 to 12 for the same reason.

Column
Column
Column
Column
JSX
import { Grid } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid columns={{ base: 1, sm: 2, lg: 4 }} gap={{ base: 'sm', lg: 'md' }}>
      <div>Column</div>
      <div>Column</div>
      <div>Column</div>
      <div>Column</div>
    </Grid>
  )
}

The .row-gap-{token} and .column-gap-{token} utilities set one axis, passed through className.

Flow

flow sets the direction a grid places its items in, rendered as the .grid-flow-* classes. A grid fills a row before it starts the next. flow="column" fills the rows of a column before it moves to the next column, so it goes with rows. An object changes the flow from a breakpoint up.

1
2
3
4
5
6
JSX
import { Grid } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid columns={2} rows={3} flow="column" gap="md">
      <div>1</div>
      <div>2</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
      <div>6</div>
    </Grid>
  )
}

flow="dense" moves a later item into a gap that an earlier, wider one left; 'row-dense' and 'column-dense' name the direction too. Below, the third item fits beside the first, where the second did not.

1
2
3
4
JSX
import { Grid, GridItem } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid columns={3} flow="dense" gap="md">
      <GridItem span={2}>1</GridItem>
      <GridItem span={2}>2</GridItem>
      <GridItem>3</GridItem>
      <GridItem>4</GridItem>
    </Grid>
  )
}

Dense placement changes where items appear, not their order in the document, which the keyboard and screen readers follow. Use it for items that read in any order, such as a gallery.

Auto-fill layout

Set fill to render .grid-fill: equal columns, as many as there are children, with no columns to count.

Column
Column
Column
Column
JSX
import { Grid } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid fill gap="md">
      <div>Column</div>
      <div>Column</div>
      <div>Column</div>
      <div>Column</div>
    </Grid>
  )
}

min sets the narrowest a column of a fill grid can get. Children that would be narrower wrap to a new row, so the column count follows the available width with no breakpoint.

Column
Column
Column
Column
Column
Column
JSX
import { Grid } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid fill min="10rem" gap="md">
      <div>Column</div>
      <div>Column</div>
      <div>Column</div>
      <div>Column</div>
      <div>Column</div>
      <div>Column</div>
    </Grid>
  )
}

Container queries

A breakpoint key of a prop, such as md in span={{ base: 'full', md: 6 }}, is a width of the viewport. A grid in a sidebar, a panel or a narrow column has less room than the viewport, and a container key follows the room it has: '@sm' to '@2xl' are the widths of sm to 2xl, measured on the nearest query container. <Grid> and <GridItem> take them beside the breakpoint keys and render the container classes of @chassis-ui/css: columns={{ '@md': 3 }} is .@md:grid-cols-3, and span={{ '@md': 4 }} is .@md:col-span-4. Where a breakpoint key and a container key both apply, the container key wins.

A query container is an ancestor with the .contains-inline class. Set contained on the grid for its default gutter and column count to follow that container too. Both boxes below hold the same grid, at any viewport width: the first is 18rem wide, under the @sm width, so its items stack, and the second is 40rem wide, so they sit side by side.

Item
Item
Item
Item
Item
Item
JSX
import { Grid, GridItem } from '@chassis-ui/react'

const Items = () => (
  <Grid contained>
    <GridItem span={{ base: 'full', '@sm': 4 }}>Item</GridItem>
    <GridItem span={{ base: 'full', '@sm': 4 }}>Item</GridItem>
    <GridItem span={{ base: 'full', '@sm': 4 }}>Item</GridItem>
  </Grid>
)

export const Example = () => {
  return (
    <>
      <div className="contains-inline mb-md" style={{ width: '18rem' }}>
        <Items />
      </div>
      <div className="contains-inline" style={{ width: '40rem' }}>
        <Items />
      </div>
    </>
  )
}

Without a query container above the grid, a container key never applies and contained leaves the gutter of the viewport.

Nesting

A <Grid> inside a <GridItem> is a grid of its own, with twelve tracks across the width of the item. It is narrower than the viewport that the gutter follows, so give it a gap, and columns in place of twelve tracks, or lay it out by its container.

Nested column
Nested column
span=4
JSX
import { Grid, GridItem } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid gap="md">
      <GridItem span={8}>
        <Grid columns={2} gap="sm">
          <div>Nested column</div>
          <div>Nested column</div>
        </Grid>
      </GridItem>
      <GridItem span={4}>span=4</GridItem>
    </Grid>
  )
}

A nested grid inherits what the outer grid sets through a custom property: a raw gap, min, and a columns above 12. It does not inherit the rest: columns from 1 to 12, rows, flow and a token gap.

Subgrid

Set subgrid on a <GridItem> to give it the column tracks it spans in the parent <Grid> instead of tracks of its own, so its children align to the outer columns. subgrid is a prop of <GridItem> because CSS subgrid only takes effect on an element that is itself a grid item of the parent. A subgrid has a gap and rows of its own: pass it the gap of the parent, or its children are narrower than the parent's tracks.

Subgrid span=4
Subgrid span=4
span=4
JSX
import { Grid, GridItem } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Grid gap="md">
      <GridItem span={8} subgrid gap="md">
        <GridItem span={4}>Subgrid span=4</GridItem>
        <GridItem span={4}>Subgrid span=4</GridItem>
      </GridItem>
      <GridItem span={4}>span=4</GridItem>
    </Grid>
  )
}

Migrating from Row/Col

<Row> and <Col> rendered the flexbox grid, which @chassis-ui/css removed in 0.7. @chassis-ui/react deprecated them in 0.3 and removes them in 0.4. Each of their props has a counterpart on <Grid> and <GridItem>:

Row and ColGrid and GridItem
<Row><Grid>, with no <Container> needed around it
<Col span={4}><GridItem span={4}>
<Col span={12}><GridItem span="full">
<Col span={12} responsive={{ md: { span: 6 } }}><GridItem span={{ base: 'full', md: 6 }}>
<Col span={4} offset={2}><GridItem span={4} start={3}>
<Col order="first"><GridItem className="order-first">
<Row> with bare <Col>s<Grid fill> with plain children
<Row cols={3}> with bare <Col>s<Grid columns={3}> with plain children
<Row cols={1} responsive={{ md: { cols: 3 } }}><Grid columns={{ base: 1, md: 3 }}>
<Row gutter="md"><Grid gap="md">
gutterX, gutterYclassName="column-gap-{token}", className="row-gap-{token}"
<Col span="auto"><Flex gap="md"> with plain children

Three differences change how a layout behaves:

  • An item with no span is one track wide. A bare <Col> shared the row with its siblings, and a <Col> with only a responsive span was as wide as the row below that breakpoint. Give such an item span="full", as the value or the base, or use a fill grid.
  • Each prop holds its own breakpoints. <Row> and <Col> took one responsive object, keyed by breakpoint, with the props inside. <Grid> and <GridItem> have no responsive prop: each prop takes an object keyed by breakpoint, with the value below the first one as base. <Grid> and <GridItem> of version 0.3 took the responsive prop too, and their props move the same way.
  • The gap applies to both axes. A <Row> with no gutter prop had 1.5rem between columns and nothing between rows. A <Grid> has the gutter of the breakpoint between both; className="row-gap-zero" removes it between rows.
  • A start line is absolute. offset pushed a column away from the one before it, so start is the offset plus one only for the first item of a row.

Scope

No content-width tracks. A grid sizes its tracks from the container, not from the content, so <Col span="auto"> has no grid counterpart: a row of items as wide as their content is a <Flex>.

Placement classes stop at twelve columns and six rows. span, start and end render classes that @chassis-ui/css generates up to 12 columns, and rowSpan, rowStart and rowEnd up to 6 rows. In a grid with more, an item past those needs an inline style={{ gridColumn: '…' }}.

No query container of its own. <Grid> makes neither itself nor its parent a query container. The container keys and contained read one from an ancestor, which the layout around the grid provides.

API

Grid

PropTypeDefault
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

PropTypeDefault
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.