Free CSS grid generator
CSS grid generator with areas that validate
Free and no account needed. Draw a page skeleton by clicking cells on a matrix of up to eight columns and six rows, size each track from 11 presets that include two minmax() forms, and take away a grid-template-areas block that has already been checked for the rectangular footprint the specification demands. The second mode swaps the fixed matrix for repeat(auto-fit, minmax()) and tells you how many columns will actually appear at a given container width, so the responsive card list is a calculation rather than a guess.
- 100% free
- No signup
- 8 × 6 matrix
- 11 track presets
- Rectangle check
Column tracks
Row tracks
Paint the areas
Click a cell to give it the selected name; click it again to empty it back to a dot.
Preview
CSS
.layout {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: 80px 1fr 80px;
gap: 12px;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
}
.header {
grid-area: header;
}
.sidebar {
grid-area: sidebar;
}
.main {
grid-area: main;
}
.aside {
grid-area: aside;
}
.footer {
grid-area: footer;
}How to write a grid template
Tracks first, then names, then decide whether the column count should be fixed or computed.
Shape the track matrix
Choose how many columns and rows you want — up to eight by six — and pick a size for each track from the preset list. The list is deliberately short and all of it is valid CSS: fractions, auto, min-content, max-content, three fixed pixel widths and two minmax() forms. Column and row gaps are set separately and collapse into a single gap value in the output when they match.
Click cells to name the areas
Select one of the six area names, then click cells in the matrix to assign it; clicking a cell that already carries the selected name empties it back to a dot, which is the null-cell token in CSS. Each name has to end up as a filled rectangle, and the tool tells you the moment one is not, because a browser discards the entire grid-template-areas declaration over a single stray cell rather than ignoring the bad name.
Switch to auto-fit if the column count should decide itself
The second mode replaces the fixed matrix with repeat(auto-fit, minmax(min, 1fr)) and reports the column count the browser will land on at the container width you set, along with the pixels each track gets above its minimum. Compare it against auto-fill in the same panel to see which one keeps empty tracks. Either mode, the Copy CSS button hands you a rule set you can paste straight into a stylesheet.
Technical specifications
| Matrix size | 1 to 8 columns by 1 to 6 rows, so 48 cells at the maximum |
|---|---|
| Column track presets | 11 values: 1fr, 2fr, 3fr, auto, min-content, max-content, 120px, 200px, 240px, minmax(120px, 1fr) and minmax(0, 1fr) |
| Row track presets | 10 values, from auto and the two intrinsic keywords through 80px, 120px and 200px to minmax(80px, auto) and minmax(120px, 1fr) |
| Named areas | 6 names available — header, sidebar, main, aside, footer and banner — each validated against its own bounding box before any CSS is written |
| Auto-fit column count | max(1, floor((container + gap) ÷ (minimum + gap))), the same integer division the browser performs on a definite container size |
| Gaps | row-gap and column-gap from 0 to 64px, written as one value when they match and as the two-value row-then-column shorthand when they differ |
| Output | grid-template-columns, grid-template-rows, the gap and a multi-line grid-template-areas block, followed by one grid-area rule per name |
| Storage | None — the matrix is held in memory only, never uploaded and never written to disk, so reloading brings back the three-by-three starting template |
Frequently asked questions
Why is my grid-template-areas declaration being ignored completely?
One of the named areas is not a rectangle, or two rows list a different number of tokens. Both make the whole declaration invalid at parse time, so the browser throws away every row rather than dropping the offending name, and your layout falls back to auto-placement as if you had never written it. An L-shaped or diagonal area is the usual cause; splitting it into two rectangles with two names fixes it. This generator checks the footprint of each name against its bounding box and refuses to emit the block until it is clean.
What is the actual difference between auto-fit and auto-fill?
They compute the same number of tracks and then disagree about the empty ones: auto-fill leaves them in place at their minimum size, auto-fit collapses them to zero so the remaining items stretch across the full width. With four tracks fitting and two cards, auto-fill leaves those two cards at the minimum track width with a hole beside them, while auto-fit gives each card half the container. Neither keyword changes anything once there are at least as many items as tracks.
Why does a 1fr column overflow instead of shrinking?
Because 1fr is shorthand for minmax(auto, 1fr), and that auto minimum resolves to the track's min-content size. A long unbroken string, a wide table or an image with an intrinsic width therefore sets a floor the track will not go below, and the grid grows past its container. Writing minmax(0, 1fr) instead pins the minimum at zero and lets the track shrink, which is why that form is in the preset list. The same reasoning applies to any fr track, not only 1fr.
Can auto-fit really replace my media queries?
For a list of cards that only needs to change column count, yes — one repeat(auto-fit, minmax(240px, 1fr)) covers every viewport width without a single breakpoint. What it cannot do is change the arrangement: moving a sidebar below the content, swapping which area is tallest or redrawing the whole template still needs a media query redefining grid-template-areas. A useful rule is that auto-fit handles quantity and media queries handle structure.
How do I place an item without giving it an area name?
Use line numbers with grid-column and grid-row, which is often lighter than naming things. grid-column: 2 / 4 spans from the second line to the fourth, grid-column: 2 / span 2 says the same thing relative to the start, and -1 counts back from the end of the explicit grid, so grid-column: 1 / -1 makes an element full width no matter how many columns there are. Named areas and line numbers can coexist in the same grid.
Why do all my rows collapse to the height of their content?
Because rows the browser creates for you are sized by grid-auto-rows, whose initial value is auto, and auto means as tall as the content. Listing sizes in grid-template-rows only sizes the rows you listed — anything beyond them is implicit and falls back to auto. Set grid-auto-rows to a fixed value or to minmax(120px, auto) to give implicit rows a floor, and give the container a height before expecting fr units in grid-template-rows to do anything, since a fraction of an undefined height is nothing.
Can grid tracks be animated?
Yes, and it is one of the few layout properties that interpolates: Chrome and Edge have animated grid-template-columns and grid-template-rows since version 107, and Firefox and Safari do it too. The condition in every engine is that both ends of the animation declare the same number of tracks with interpolable sizes — animating from three tracks to four, or from a keyword to a length, snaps instead of sliding. Because each frame is a full layout pass it is far more expensive than moving a transform, so keep it to short, deliberate transitions.
About grid templates, named areas and auto-fit
grid-template-areas is the only place in CSS where the declaration looks like the thing it describes. Each string is a row, each token in it is a cell, and a dot marks a cell that belongs to nobody — so the rule reads as a wireframe and a reviewer can see the layout without opening the browser. The specification attaches two conditions to that readability. Every row must contain the same number of tokens, and every name must occupy a solid rectangle. Fail either and the declaration is invalid as a whole: the browser does not salvage the good rows, it drops all of them and places your children by auto-flow instead, which is why the symptom is usually a layout that looks like the template was never applied. Painting an L shape is the easy way to trip it, and the fix is always the same — two names, two rectangles.
The other half of grid is track sizing, where fr is less obvious than it looks. A fraction unit distributes what is left after every fixed track, every gap and every intrinsic minimum has been paid for, and 1fr is really minmax(auto, 1fr), so its floor is the track's min-content size. That floor is what makes an fr column refuse to shrink around a long word, and minmax(0, 1fr) is the form that removes it. In auto-fit mode the same minimum drives the column count: the browser divides the container plus one gap by the track minimum plus one gap and takes the integer part, which is the number this page prints for you. One caveat that catches almost everyone shipping that pattern — repeat(auto-fit, minmax(240px, 1fr)) overflows any container narrower than 240px, including a phone in portrait, and the guard is to write minmax(min(100%, 240px), 1fr) instead. Get that right and a card list needs no breakpoints at all; restructuring the template still does.
Grid earns its place when the alignment has to hold in both directions — a sidebar whose top edge lines up with the header below it, a card footer that sits at the same height across a row of unequal cards. When only one axis matters, a flex container is less machinery for the same result, and the two nest happily: grid for the page, flex inside a cell. Cells that hold media usually want a fixed proportion rather than a fixed height, which the aspect ratio calculator works out, and if you do animate a track list, the easing curve matters more than the duration because a layout animation reads as sluggish long before a transform does. A layout tool looks like an odd neighbor on a color hub until you remember what goes into the tracks: surfaces, borders and text that all need values from somewhere, which is the job of the palette generator and the color wheel.
Where the template is generated
Every number on this page is worked out by JavaScript running in the tab you are reading it in. Nothing you type, paste or open is uploaded, logged or kept, which is also why the tools carry on working after you disconnect from the network.
Painting cells changes a two-dimensional array in memory and nothing else. There is no account holding your templates, no autosave and no analytics event describing the layout you drew, so closing the tab is all it takes to discard it.