Docs
Next

Flex grid

Wrap items into equal columns with flex-grid, and draw divider lines between them.

On this page

flex-grid is a wrapping flex container whose children size themselves as equal columns. Use it instead of CSS grid when you want divider lines between the items: each item draws its own divider, so the lines follow the items as they wrap.

Quick referenceLink to this section

ClassStyles
flex-grid& { --tw-flex-grid-gap-x: 0px; --tw-flex-grid-gap-y: 0px; --tw-flex-grid-cols: 1; display: flex; flex-direction: row; flex-wrap: wrap; overflow: hidden; column-gap: calc(var(--tw-flex-grid-gap-x) - 1px); row-gap: calc(var(--tw-flex-grid-gap-y) - 1px); }.flex-grid { > * { width: round(down, calc((100% / var(--tw-flex-grid-cols)) - ((var(--tw-flex-grid-cols) - 1) * var(--tw-flex-grid-gap-x)) / var(--tw-flex-grid-cols)), 1px); } }
flex-grid-col-span-1width: calc((100% / (var(--tw-flex-grid-cols) / 1)) - ((var(--tw-flex-grid-cols) / 1 - 1) * var(--tw-flex-grid-gap-x)) / (var(--tw-flex-grid-cols) / 1));
flex-grid-col-span-2width: calc((100% / (var(--tw-flex-grid-cols) / 2)) - ((var(--tw-flex-grid-cols) / 2 - 1) * var(--tw-flex-grid-gap-x)) / (var(--tw-flex-grid-cols) / 2));
flex-grid-col-span-3width: calc((100% / (var(--tw-flex-grid-cols) / 3)) - ((var(--tw-flex-grid-cols) / 3 - 1) * var(--tw-flex-grid-gap-x)) / (var(--tw-flex-grid-cols) / 3));
flex-grid-col-span-4width: calc((100% / (var(--tw-flex-grid-cols) / 4)) - ((var(--tw-flex-grid-cols) / 4 - 1) * var(--tw-flex-grid-gap-x)) / (var(--tw-flex-grid-cols) / 4));
flex-grid-col-span-5width: calc((100% / (var(--tw-flex-grid-cols) / 5)) - ((var(--tw-flex-grid-cols) / 5 - 1) * var(--tw-flex-grid-gap-x)) / (var(--tw-flex-grid-cols) / 5));
flex-grid-col-span-6width: calc((100% / (var(--tw-flex-grid-cols) / 6)) - ((var(--tw-flex-grid-cols) / 6 - 1) * var(--tw-flex-grid-gap-x)) / (var(--tw-flex-grid-cols) / 6));
flex-grid-cols-1--tw-flex-grid-cols: 1;
flex-grid-cols-2--tw-flex-grid-cols: 2;
flex-grid-cols-3--tw-flex-grid-cols: 3;
flex-grid-cols-4--tw-flex-grid-cols: 4;
flex-grid-cols-5--tw-flex-grid-cols: 5;
flex-grid-cols-6--tw-flex-grid-cols: 6;
flex-grid-divide-black& > ::after { background-color: #000; }
flex-grid-divide-transparent& > ::after { background-color: transparent; }
flex-grid-divide-white& > ::after { background-color: #fff; }
flex-grid-divide-x& > * { position: relative; }.flex-grid-divide-x > * { &::after { content: var(--tw-content); position: absolute; top: 0; height: 100%; right: calc(-1 * (var(--tw-flex-grid-gap-x) / 2)); transform: translateX(50%); width: 1px; } }.flex-grid-divide-x > * { &:last-child::after { display: none; } }
flex-grid-divide-x-0& > * { position: relative; }.flex-grid-divide-x-0 > * { &::after { content: var(--tw-content); position: absolute; top: 0; height: 100%; right: calc(-1 * (var(--tw-flex-grid-gap-x) / 2)); transform: translateX(50%); width: 0px; } }.flex-grid-divide-x-0 > * { &:last-child::after { display: none; } }
flex-grid-divide-x-2& > * { position: relative; }.flex-grid-divide-x-2 > * { &::after { content: var(--tw-content); position: absolute; top: 0; height: 100%; right: calc(-1 * (var(--tw-flex-grid-gap-x) / 2)); transform: translateX(50%); width: 2px; } }.flex-grid-divide-x-2 > * { &:last-child::after { display: none; } }
flex-grid-divide-x-4& > * { position: relative; }.flex-grid-divide-x-4 > * { &::after { content: var(--tw-content); position: absolute; top: 0; height: 100%; right: calc(-1 * (var(--tw-flex-grid-gap-x) / 2)); transform: translateX(50%); width: 4px; } }.flex-grid-divide-x-4 > * { &:last-child::after { display: none; } }
flex-grid-divide-x-8& > * { position: relative; }.flex-grid-divide-x-8 > * { &::after { content: var(--tw-content); position: absolute; top: 0; height: 100%; right: calc(-1 * (var(--tw-flex-grid-gap-x) / 2)); transform: translateX(50%); width: 8px; } }.flex-grid-divide-x-8 > * { &:last-child::after { display: none; } }
flex-grid-divide-y& > * { position: relative; }.flex-grid-divide-y > * { &::after { content: var(--tw-content); position: absolute; left: 0; width: 100%; bottom: calc(-1 * (var(--tw-flex-grid-gap-y) / 2)); transform: translateY(50%); height: 1px; } }.flex-grid-divide-y > * { &:last-child::after { display: none; } }
flex-grid-divide-y-0& > * { position: relative; }.flex-grid-divide-y-0 > * { &::after { content: var(--tw-content); position: absolute; left: 0; width: 100%; bottom: calc(-1 * (var(--tw-flex-grid-gap-y) / 2)); transform: translateY(50%); height: 0px; } }.flex-grid-divide-y-0 > * { &:last-child::after { display: none; } }
flex-grid-divide-y-2& > * { position: relative; }.flex-grid-divide-y-2 > * { &::after { content: var(--tw-content); position: absolute; left: 0; width: 100%; bottom: calc(-1 * (var(--tw-flex-grid-gap-y) / 2)); transform: translateY(50%); height: 2px; } }.flex-grid-divide-y-2 > * { &:last-child::after { display: none; } }
flex-grid-divide-y-4& > * { position: relative; }.flex-grid-divide-y-4 > * { &::after { content: var(--tw-content); position: absolute; left: 0; width: 100%; bottom: calc(-1 * (var(--tw-flex-grid-gap-y) / 2)); transform: translateY(50%); height: 4px; } }.flex-grid-divide-y-4 > * { &:last-child::after { display: none; } }
flex-grid-divide-y-8& > * { position: relative; }.flex-grid-divide-y-8 > * { &::after { content: var(--tw-content); position: absolute; left: 0; width: 100%; bottom: calc(-1 * (var(--tw-flex-grid-gap-y) / 2)); transform: translateY(50%); height: 8px; } }.flex-grid-divide-y-8 > * { &:last-child::after { display: none; } }

Basic usageLink to this section

Add flex-grid and a column count such as flex-grid-cols-3. Every direct child becomes one column wide; a partly filled last row keeps the same column width.

flex-grid flex-grid-cols-3
01
02
03
04
05
export default function Demo() {
  return (
    <div className="flex-grid flex-grid-cols-3 w-full max-w-[560px]">
      {['01', '02', '03', '04', '05'].map((label, i) => (
        <div
          key={label}
          className={`grid h-16 place-items-center font-mono text-sm text-white ${i % 2 ? 'bg-theme-700' : 'bg-theme-500'}`}
        >
          {label}
        </div>
      ))}
    </div>
  )
}

GapsLink to this section

Set the gap with an arbitrary value or a CSS variable: flex-grid-gap-[24px] or flex-grid-gap-(--gap-md). It sets the column and the row gap, and the column widths account for it. The hatched area is the gap.

flex-grid-gap-(--gap-md)
01
02
03
04
05
06
const hatch =
  'bg-[repeating-linear-gradient(315deg,var(--color-foundations-line-muted)_0_1px,transparent_0_50%)] bg-size-[10px_10px]'

export default function Demo() {
  return (
    <div className={`flex-grid flex-grid-cols-3 flex-grid-gap-(--gap-md) w-full max-w-[560px] ${hatch}`}>
      {['01', '02', '03', '04', '05', '06'].map((label) => (
        <div
          key={label}
          className="bg-theme-500 grid h-16 place-items-center rounded-lg font-mono text-sm text-white"
        >
          {label}
        </div>
      ))}
    </div>
  )
}

Spanning columnsLink to this section

flex-grid-col-span-{n} makes an item n columns wide, gaps included.

flex-grid-col-span-2
flex-grid-col-span-2
02
03
04
export default function Demo() {
  return (
    <div className="flex-grid flex-grid-cols-3 flex-grid-gap-[16px] w-full max-w-[560px]">
      <div className="bg-theme-700 flex-grid-col-span-2 grid h-16 place-items-center rounded-lg font-mono text-sm text-white">
        flex-grid-col-span-2
      </div>
      {['02', '03', '04'].map((label) => (
        <div
          key={label}
          className="bg-theme-500 grid h-16 place-items-center rounded-lg font-mono text-sm text-white"
        >
          {label}
        </div>
      ))}
    </div>
  )
}

DividersLink to this section

flex-grid-divide-x draws a vertical line in the middle of the gap after each item, and flex-grid-divide-y a horizontal line under each item. Set the width with a suffix (-0, -2, -4, -8; the bare class is 1px) and the color with flex-grid-divide-{color}. The container clips its overflow, so the line after the last item of a row falls outside it and is not drawn, and the last item draws none at all.

The dividers are ::after elements whose content comes from Tailwind's --tw-content variable. Tailwind only defines that variable when a project uses a content-* utility somewhere, so add *:after:content-[''] to the container to be sure the lines render:

flex-grid-divide-x
12kPages served
98%Lighthouse score
4Languages
export default function Demo() {
  return (
    <div className="flex-grid flex-grid-cols-3 flex-grid-gap-[48px] flex-grid-divide-x flex-grid-divide-(--color-foundations-surface-border) w-full max-w-[600px] *:after:content-['']">
      {[
        ['12k', 'Pages served'],
        ['98%', 'Lighthouse score'],
        ['4', 'Languages'],
      ].map(([value, label]) => (
        <div key={label} className="flex flex-col gap-1">
          <span className="text-h3 color-heading">{value}</span>
          <span className="text-small color-body">{label}</span>
        </div>
      ))}
    </div>
  )
}
flex-grid-divide-y-2

Tokens

Components

Payload blocks

export default function Demo() {
  return (
    <div className="flex-grid flex-grid-cols-1 flex-grid-gap-[24px] flex-grid-divide-y-2 flex-grid-divide-black/20 w-full max-w-[420px] *:after:content-['']">
      {['Tokens', 'Components', 'Payload blocks'].map((label) => (
        <p key={label} className="text-body color-body">
          {label}
        </p>
      ))}
    </div>
  )
}

Each item has one ::after, so flex-grid-divide-x and flex-grid-divide-y cannot be combined on one container; the class that comes later in the CSS wins.

The colors that work are black, white and transparent, an arbitrary value (flex-grid-divide-[#d4d4d4]) or a CSS variable (flex-grid-divide-(--color-foundations-surface-border)), each with an optional opacity modifier (flex-grid-divide-black/20).

Responsive and state variantsLink to this section

Every flex-grid-* utility takes breakpoint prefixes. A common pattern is one column on phones and more from a breakpoint up:

flex-grid-cols-1 md:flex-grid-cols-2 lg:flex-grid-cols-4
export default function Demo() {
  return (
    <div className="flex-grid flex-grid-cols-1 flex-grid-gap-[16px] w-full md:flex-grid-cols-2 lg:flex-grid-cols-4">
      {['01', '02', '03', '04'].map((label) => (
        <div
          key={label}
          className="bg-theme-500 grid h-16 place-items-center rounded-lg font-mono text-sm text-white"
        >
          {label}
        </div>
      ))}
    </div>
  )
}

State variants work too, for example hover:flex-grid-divide-black to darken the lines while the pointer is over the grid.

CustomizingLink to this section

The table lists counts and spans from 1 to 6, but any whole number works (flex-grid-cols-12, flex-grid-col-span-8). The divider widths come from Tailwind's divide-width scale. The gap takes any length, so pass a token variable such as --gap-md or --grid-default-gap to stay on the token scale (see Gap).