CSS Grid Generator: A Practical Guide to Columns, fr Units and Gap
Learn CSS Grid the way you actually use it — grid-template-columns and rows, the fr unit, gap, and when to reach for Grid over Flexbox. With a real before/after example.
CSS Grid Generator: A Practical Guide to Columns, fr Units and Gap
I spent years writing layouts with floats, then with Flexbox hacks, before I trusted CSS Grid enough to put it in production. The thing that finally clicked was realizing Grid is not a more complicated Flexbox — it is a different tool for a different shape of problem. Flexbox arranges things along one line. Grid arranges things on a plane, with rows and columns you define up front. Once that distinction lands, most "why is my layout fighting me" frustration disappears.
This guide walks through the four pieces you touch every single day — grid-template-columns, grid-template-rows, the fr unit, and gap — then settles the Grid-vs-Flexbox question with a rule you can apply without thinking. If you want to skip the typing and just drag tracks around, the CSS Grid Generator renders a live preview and hands you the exact CSS to paste.
grid-template-columns and grid-template-rows: defining the skeleton
A grid container starts with one declaration: display: grid. Everything after that is you describing the tracks. grid-template-columns lists the column widths left to right; grid-template-rows lists the row heights top to bottom. Each value in the list is one track.
.container {
display: grid;
grid-template-columns: 240px 1fr 240px;
grid-template-rows: auto 1fr auto;
}
That snippet describes a three-column shell: a fixed 240px left rail, a stretchy middle, a fixed 240px right rail, with a header row that hugs its content, a body that fills the leftover height, and a footer that hugs its content again. You did not place a single child element yet — the grid auto-flows them into the cells in source order. That alone covers a huge share of page shells.
A track can be almost any length: pixels, rems, percentages, auto, min-content, max-content, minmax(), or the fraction unit we are about to get into. You can also repeat tracks instead of typing them out: repeat(12, 1fr) builds the classic 12-column grid in five characters of intent instead of twelve copies of 1fr.
The fr unit: fractions of the leftover space
The fr unit is the piece newcomers misread most often. 1fr does not mean "one column" or "one equal share of the container." It means one fraction of the space that remains after fixed-size tracks and gaps are subtracted.
Consider grid-template-columns: 200px 1fr 2fr. The browser first reserves 200px for the first track, subtracts the gaps, then splits everything left into three fractions: the 1fr track gets one, the 2fr track gets two. If the container is 1100px wide with no gap, the leftover is 900px, so the second column is 300px and the third is 600px. Resize the window and those two recompute automatically while the 200px rail stays put.
This is why repeat(3, 1fr) gives you three genuinely equal columns no matter the viewport — three equal fractions of the leftover space. Per the MDN documentation on fr, the unit represents a fraction of the leftover space in the grid container, which is exactly why it adapts where a percentage would not: percentages are computed against the whole container including gaps, so 33.33% columns with a gap overflow, while 1fr columns never do.
One gotcha worth memorizing: a bare 1fr is shorthand for minmax(auto, 1fr), and that auto floor refuses to shrink below the widest unbreakable child. Drop a long URL or a wide image into a 1fr track and it blows out the layout. The fix is almost always minmax(0, 1fr), which lets the track shrink to zero so overflow handling inside the cell can do its job.
gap: the spacing that replaced margin hacks
gap sets the gutter between tracks — and only between them, never on the outer edges. That single property retired a decade of :not(:last-child) margin tricks.
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 24px;
}
You can split it: gap: 16px 24px is 16px between rows and 24px between columns (row gap first, then column gap), or set each with row-gap and column-gap. Because the gutter is built into the grid math, the fr calculation already accounts for it — you never have to subtract gap widths by hand the way you did with percentage-and-margin layouts. That auto-fit line above, paired with gap, is a complete responsive gallery: it packs in as many ~240px columns as fit and reflows on resize with zero media queries.
A real before/after: a card row that overflows
Here is a bug I have debugged for three different teammates. A three-card product row keeps blowing past its container on certain screens. The CSS looks innocent:
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
The culprit is a single card holding a long unbreakable string — a product URL like https://store.example.com/p/wireless-noise-cancelling-headphones-v3-graphite. That string forces its 1fr track wider than its share, the other two tracks compensate, and the whole row spills out of the parent with a horizontal scrollbar nobody asked for.
The fix is one character of intent:
.cards {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
minmax(0, 1fr) drops the auto floor to zero, so the track now shrinks as needed and overflow: hidden or word-break: break-word inside the card finally takes effect. Rebuilding that exact grid in the generator and flipping the track in the preview turns an abstract spec gotcha into a before/after you can watch snap back into place.
Grid vs Flexbox: the rule that ends the debate
You do not pick one for the whole app — you pick per layout, and the two compose freely. The rule I give every junior dev:
- **If you can sketch the layout as a table on graph paper — rows and columns, regions that span both axes — it is a Grid.** Page shells, dashboards, the holy-grail header/nav/main/aside/footer, an image mosaic, a calendar.
- If you would describe it as "a row of things that wrap" or "a stack of things" — it is Flexbox. A nav bar, a tag cloud, a toolbar, a vertical form, centering one element.
Most real apps end up using Grid for the page skeleton and Flexbox inside each cell. Grid places the sidebar and main region; Flex lays out the buttons inside the toolbar that lives in the header cell. Reaching for Grid on a one-dimensional wrapping row means you fight track counts at every breakpoint when flex-wrap would have handled it for free — and reaching for Flexbox on a two-axis layout means nudging margins forever to fake what grid-template-areas does declaratively.
If you are working inside a utility-class setup, the generated track string drops straight into a Tailwind arbitrary value like grid-cols-[280px_1fr] — handy to keep next to the Tailwind CSS cheatsheet while you wire things up.
Generating the CSS without the guesswork
The fastest path from idea to shippable CSS is to set rows, columns, and gap visually, watch the preview render with the exact values that will land in your stylesheet, name a few cells into grid-template-areas if it is a page shell, and copy the whole block. What used to be twenty minutes of counting line numbers and re-checking MDN becomes about ninety seconds, and the output reads cleanly in code review because named areas describe the layout in plain words.
Grid rewards a small amount of upfront learning with layouts that survive resizing, reordering, and real content. Get grid-template-columns, fr, gap, and the Grid-vs-Flexbox instinct into muscle memory, lean on minmax(0, 1fr) when content fights back, and you will reach for it first instead of last.
Made by Toolora · Updated 2026-06-13