Docs
Systhema Design (opens in new tab)
Unreleased

Grid

Lay content out on the token column grid with grid-default, and inset it by whole columns with col-mx-*.

On this page

The grid utilities put content on the layout grid from your tokens: the same column count, gutter and margin the design was drawn on. grid-default creates the grid, and col-mx-* insets an item by a number of columns on each side, which is how Section narrows its content column.

Quick referenceLink to this section

ClassStyles
grid-defaultdisplay: grid;column-gap: var(--grid-default-gap);grid-template-columns: repeat(var(--grid-default-count), minmax(0, 1fr));
grid-cols-defaultgrid-template-columns: repeat(var(--grid-default-count), minmax(0, 1fr));
gap-grid-defaultgap: var(--grid-default-gap);
gap-x-grid-defaultcolumn-gap: var(--grid-default-gap);
gap-y-grid-defaultrow-gap: var(--grid-default-gap);
col-mx-0& { grid-column-start: 1; grid-column: span 12 / span 12; }@media (width >= 640px) { & { grid-column: span 24 / span 24; grid-column-start: 1; } }
col-mx-1& { grid-column-start: 1; grid-column: span 12 / span 12; }@media (width >= 640px) { & { grid-column: span 22 / span 22; grid-column-start: 2; } }
col-mx-2& { grid-column-start: 1; grid-column: span 12 / span 12; }@media (width >= 640px) { & { grid-column: span 20 / span 20; grid-column-start: 3; } }
col-mx-3& { grid-column-start: 1; grid-column: span 12 / span 12; }@media (width >= 640px) { & { grid-column: span 18 / span 18; grid-column-start: 4; } }
col-mx-4& { grid-column-start: 1; grid-column: span 12 / span 12; }@media (width >= 640px) { & { grid-column: span 16 / span 16; grid-column-start: 5; } }
col-mx-5& { grid-column-start: 1; grid-column: span 12 / span 12; }@media (width >= 640px) { & { grid-column: span 14 / span 14; grid-column-start: 6; } }
col-mx-6& { grid-column-start: 1; grid-column: span 12 / span 12; }@media (width >= 640px) { & { grid-column: span 12 / span 12; grid-column-start: 7; } }

Basic usageLink to this section

Add grid-default to a container to get a CSS grid with the token column count and gutter. Combine it with container to sit the grid inside the page margins, the way Section does. With the default tokens the grid has 12 columns on phones and 24 from md up; switch the preview between device widths to see the columns change.

grid-default container
export default function Demo() {
  return (
    <div className="w-full py-8">
      <div className="grid-default container">
        {Array.from({ length: 24 }, (_, i) => (
          <div
            key={i}
            className="bg-theme-200 text-theme-800 grid h-20 place-items-center rounded-sm font-mono text-[10px]"
          >
            {i + 1}
          </div>
        ))}
      </div>
    </div>
  )
}

grid-default sets the column gap only. Add gap-y-grid-default when items wrap onto more than one row and should have the same gutter between rows, or gap-grid-default for both.

Spanning columnsLink to this section

Items on the grid take Tailwind's col-span-* and col-start-* utilities. Count in grid columns of the current breakpoint, so a span usually needs a responsive pair: col-span-12 fills the phone grid, md:col-span-8 takes a third of the 24-column grid.

Spanning columns
export default function Demo() {
  return (
    <div className="w-full py-8">
      <div className="grid-default container gap-y-grid-default">
        {['01', '02', '03'].map((label) => (
          <div
            key={label}
            className="bg-theme-500 col-span-12 grid h-20 place-items-center rounded-lg font-mono text-sm text-white md:col-span-8"
          >
            {label}
          </div>
        ))}
      </div>
    </div>
  )
}

Insetting by columnsLink to this section

col-mx-{n} spans an item across the grid minus n columns on each side, from the md breakpoint up. Below md every col-mx-* spans the whole grid, so content stays full width on phones. <Section padding={2}> renders col-mx-2 on its content column.

col-mx-0 to col-mx-4
const hatch =
  'bg-[repeating-linear-gradient(315deg,var(--color-foundations-line-muted)_0_1px,transparent_0_50%)] bg-size-[10px_10px]'

const insets = ['col-mx-0', 'col-mx-1', 'col-mx-2', 'col-mx-3', 'col-mx-4']

export default function Demo() {
  return (
    <div className="w-full py-8">
      <div className={`grid-default container gap-y-grid-default rounded-lg ${hatch}`}>
        {insets.map((inset) => (
          <div
            key={inset}
            className={`bg-theme-500 grid h-10 place-items-center rounded-md font-mono text-xs text-white ${inset}`}
          >
            {inset}
          </div>
        ))}
      </div>
    </div>
  )
}

The classes go up to col-mx-6 by default (see Customizing).

Using the grid tokens on your own gridLink to this section

grid-cols-default, gap-grid-default, gap-x-grid-default and gap-y-grid-default set one property each from the same tokens. Use them when you build a grid with other settings, for example with Tailwind's grid and a row gap of your own:

<div class="grid grid-cols-default gap-x-grid-default gap-y-8">…</div>

Responsive and state variantsLink to this section

The column count, gutter and margin are responsiveSizing tokens, so the grid changes per breakpoint without a prefix. With the default tokens:

BreakpointColumnsGutter (--grid-default-gap)Margin (--grid-default-margin)
sm1216px17px
md2412px30px
lg2420px78px

col-mx-* already switches at md. Prefix it to start an inset later, for example col-mx-0 lg:col-mx-3 keeps the item full width up to lg. Every grid class takes breakpoint prefixes the same way (md:grid-default).

CustomizingLink to this section

The grid reads grid.default.count, grid.default.gap and grid.default.margin from the responsiveSizing collection (see Spacing and sizing and Breakpoints). Override them per breakpoint with customTokens.responsiveSizing:

systhema.config.ts
const config: SysthemaConfig = {
  customTokens: {
    responsiveSizing: {
      xl: {
        grid: { default: { count: '24', gap: '20px', margin: '142px' } },
      },
    },
  },
}

col-mx-* follows the count of each breakpoint: a breakpoint with its own count gets its own inset rules. Each other key in the tokens' grid group becomes a grid of its own, with grid-<name>, grid-cols-<name> and gap-grid-<name> classes.

The number of col-mx-* steps is blocks.grid.paddingCols (default 6). With a 24-column grid, values up to 11 leave at least two columns:

systhema.config.ts
const config: SysthemaConfig = {
  blocks: {
    grid: { enabled: true, paddingCols: 8 },
  },
}