Why CSS grid doesn’t work for a 12-column layout system
Gaps are fixed-width, and 11 of them is all it takes to overflow a phone.
Every design system I’ve built eventually needs a 12-column grid, and CSS grid looks made for the job. grid-template-columns: repeat(12, 1fr) spells out the shape directly. So that’s where I started.
Where it breaks
On most laptop viewports, a 12-column grid looks fine. Pull the slider below to shrink the container, and you’ll see items push past the right edge around 352px.
CSS grid builds a fixed-width scaffold. Gaps are set in concrete — they don’t shrink when the container gets smaller, they don’t wrap, they don’t collapse. For 12 columns, that’s 11 gaps × 32px (gap-8) = 352px of pure gutter. Narrower than that, and the grid spills.
What about container queries?
The obvious refinement: @containerqueries that swap the column count at smaller widths — 12 → 4 → 2. Better than viewport media queries, since a grid inside a sidebar shouldn’t care about viewport width. But it doesn’t actually solve the problem. The layout snaps at thresholds instead of reflowing continuously, and spans don’t translate across breakpoints — half on a 12-col grid is col-span-6, on a 4-col grid it’s col-span-2, so every item has to restate its span at every breakpoint.
There’s also repeat(auto-fit, minmax(MIN, 1fr)) — a CSS grid pattern that fits as many equal columns as the container allows and drops one when it runs out. No breakpoints, no overflow. The catch: every column is the same width. You can’t put a one-third card next to a two-thirds card in the same row, which is the whole point of a 12-column grid.
Why flex, not CSS grid
A grid primitive needs three things: continuous reflow as the container changes, per-item widths (a third here, a half there), and no overflow at any container size.
CSS grid gives you per-item widths but fights the other two. auto-fit gives you reflow and safety but locks you into uniform columns. Flex-wrap with percentage widths is the only approach that delivers all three.
Teul builds on flex flex-wrap. Each item declares its own percentage width; items that don’t fit wrap to the next row, and the gaps wrap with them instead of sitting fixed in the container.
The width formula falls out of that: calc(size/12 × (100% + colGap) − colGap). The + colGap term reserves a gap-sized chunk for each 1/12 slot; the − colGap term subtracts the trailing gap that the last item in a row doesn’t need. When items wrap, the gaps wrap with them, and the formula stays the same.