Skip to content
PolyFish CSS

We didn't ship a grid system

Twelve columns solve a problem most pages don't have. What we shipped instead, and when column classes are still the right answer.

· ~5 min read · 0.16.0 in the changelog

Every framework I've used in fifteen years shipped a twelve-column grid, and on most of the pages I built with them I used about four of those columns. You learn the incantation — col-md-8 col-lg-6 — and after a while you stop noticing that you are describing a layout in units that have nothing to do with the content.

So when it came to it, we left the grid system out. There are column classes, and I'll get to when they're the right call, but they are not how you are expected to lay out a page here.

Columns answer a question nobody asked

"How many twelfths of the container should this take at a medium viewport" is a strange thing to know about a card. The useful question is what should happen when there isn't room — and a column span can't express that. It can only say "become full width at a breakpoint", which is why every twelve-column layout eventually grows a pile of col-sm-12 col-md-6 col-lg-4 and nobody can picture the result without loading it.

What we ship instead is a handful of layouts that each answer the runs-out-of-room question differently. A cluster wraps item by item. A switcher goes from a row to a column all at once, so you never get two items on one line and a lonely third underneath. A sidebar stacks only when the main part would get too narrow to read. None of those need a breakpoint and none of them need you to count anything.

The grid that drops a column

Card grids are the one place a count genuinely matters, and even there we treat it as a ceiling rather than a promise. grid-template-columns with a minmax() floor lets the grid work out that three columns don't fit before it makes three columns that don't work:

grid-template-columns: repeat(auto-fit, minmax(max(
  min(var(--pf-col-min, 17rem), 100%),
  (100% - (var(--pf-cols, 1) - 1) * var(--pf-gap)) / var(--pf-cols, 1)
), 1fr));

It looks worse than it is. The inner division is "what each column would be at the requested count"; the max() refuses to go below the readable minimum; auto-fit then fits as many as it can. Ask for three, give it 940px and a generous gap, and you get two — which is what you wanted, because three would have been 226px each and body text at 226px breaks every three words. I know that number precisely because we shipped it.

When the columns are right

There is one case, and it's a real one: you are rebuilding an existing layout that was designed on a twelve-column grid, with a design file that says so, and the fastest honest path is to reproduce it. .pf-row and .pf-col-* are there for that, they behave the way you expect, and they are not deprecated.

Watch the gutters, though. Twelve tracks means eleven gutters, and if you hand that row a generous page gap you can get eleven gutters wider than a phone — at which point every track computes to zero and your content sails off the side. Ask me how I know. The row keeps its own narrower gutter for exactly this reason.

The honest trade

You give up a vocabulary that transfers between projects. Someone who knows Bootstrap's grid can read a Bootstrap layout in any codebase, and that's worth something real. What you get back is markup where the class says what the layout does rather than how many twelfths it occupies, and a page that responds to its container instead of to the window it happens to be in.

What this post does not cover

  • Subgrid, which solves a different problem and is not used here.
  • Masonry layout, which is still moving in the specification.

Checked against twelve-column grid, readable minimum. Code blocks are reproduced from the PolyFishCSS source files, not reconstructed from description. Figures are measured from the build they describe.

What this doesn’t cover

If you are reproducing a design that was drawn on a twelve-column grid, none of this is an argument — you need the columns, and until 0.20 we shipped them. What replaced them cannot express an arbitrary span, and the escape hatch is a few lines of grid in your own stylesheet rather than a class.

Sources: MDN on minmax() and MDN on CSS grid layout. The grid expression is reproduced verbatim from PolyFishCSS-Core.css — not reconstructed from description.

Layout Grid Primitives Design CSS Frameworks