CSS Grid generator
Build a CSS Grid layout visually: set the columns, rows, track sizes and gaps, drag items across cells to place them, and copy the generated CSS and the matching HTML.
Grid tracks
Preview and item placement
Click a cell to place an item, or drag across cells to span them. With the keyboard, press Enter on one cell, then on the opposite corner; Escape cancels. Empty cells are given a minimum height here so you can see them.
- column 1 / 4 · row 1 / 2
- column 1 / 2 · row 2 / 3
- column 2 / 4 · row 2 / 3
Generated code
Each item is placed with grid-column and grid-row, counting grid lines from 1.
.grid-container {
display: grid;
grid-template-columns: 200px repeat(2, 1fr);
grid-template-rows: auto 1fr auto;
gap: 16px;
}
.header {
grid-column: 1 / 4;
grid-row: 1 / 2;
}
.sidebar {
grid-column: 1 / 2;
grid-row: 2 / 3;
}
.main {
grid-column: 2 / 4;
grid-row: 2 / 3;
}<div class="grid-container"> <div class="header">header</div> <div class="sidebar">sidebar</div> <div class="main">main</div> </div>
Build a CSS Grid layout visually: set the columns, rows, track sizes and gaps, drag items across cells to place them, and copy the generated CSS and the matching HTML.
On this page
How to use this tool#
- Start from a layout or from scratch. The tool opens on a half-built page layout with its bottom row still free. The four buttons at the top swap in a finished layout you can take apart, and Reset brings back the starting grid.
- Set how many columns and rows you want, up to 12 of each, then give every track a size. Each field takes any CSS track size:
1fr,200px,auto,25%,min-content,minmax(200px, 1fr)orfit-content(300px). A field with an invalid value turns red and is left out of the output until you fix it. - Set the gaps. Column gap and row gap are separate fields, and the output writes them as one
gapdeclaration when they match. - Place items on the grid. Click an empty cell to put one item there, or drag across several cells to make an item span them. With the keyboard, tab to a cell, press Enter, then press Enter on the opposite corner; Escape cancels.
- Rename or remove items. The list under the preview holds every item with its name and the lines it sits on. The name becomes the class in the CSS and the area name when you switch to named areas.
- Choose how the items are placed in the generated code: line numbers (
grid-columnandgrid-row) or a named-area map (grid-template-areas). Copy the CSS and the HTML with the buttons beside each box.
The preview is a real CSS Grid, so it shows the same layout your browser will paint. The one liberty it takes is a minimum height on empty cells, so an empty auto row is still visible while you work on it.
What CSS Grid does#
CSS Grid Layout divides a container into rows and columns and lets you place children into that structure, in two dimensions at once. Flexbox arranges things along one axis and wraps when it runs out of room; Grid lets you say "this element starts at column line 2 and ends at line 4, on row 1", independently of where the element sits in the HTML.
A grid starts with display: grid on a container. Everything that follows is about sizing the tracks and placing the children.
.grid-container {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
gap: 16px;
}That is three columns, of which the middle one takes whatever space is left, and three rows, of which the middle one does the same. Items dropped in with no placement rules fill the cells one after another in source order.
Tracks, lines, cells and areas#
Four words cover almost everything, and the tool above uses all of them:
- Track: one column or one row.
grid-template-columnsandgrid-template-rowsare lists of track sizes. - Line: the boundary between two tracks, and the outer edges. Lines are numbered from 1 at the start edge. A grid with three columns has four column lines. This is the part people get wrong:
grid-column: 1 / 3is not "columns 1 to 3", it is "from line 1 to line 3", which covers two columns. Turn on Line numbers in the preview above to see them drawn on the grid. - Cell: the space between two adjacent column lines and two adjacent row lines.
- Area: any rectangle of one or more cells. An item always occupies a rectangle; Grid has no L-shaped items.
Lines also count backwards. -1 is the last line of the explicit grid, the one your template declared, so grid-column: 1 / -1 is the reliable way to say "the full width", however many columns there turn out to be. Negative numbers do not count implicit tracks, the ones the browser creates on its own for items placed past the end of your template.
Sizing tracks#
Every field in the Column sizes and Row sizes rows above takes one of these:
- A length such as
200px,1.5remor10vh. Fixed, and ignored when free space is shared out. - A percentage such as
25%, resolved against the content box of the grid container on that axis. auto: as a maximum it behaves likemax-content, but anautotrack can also be stretched byalign-contentorjustify-content, which is why a singleautocolumn often ends up filling the container.min-contentandmax-content: the smallest the content can get without overflowing (roughly, the longest word) and the size it would take with no wrapping at all.1fr, the flexible unit, described below.minmax(min, max)andfit-content(limit), described below.
The fr unit
fr is a fraction of the leftover space: the space that remains after fixed tracks and gaps have been taken out. Each flexible track gets its flex factor divided by the sum of all the flex factors. So in 200px 1fr 2fr with a 800px container and no gaps, the last two tracks share 600px in a 1:2 ratio, giving 200px and 400px.
Because it is a share of what is left, 1fr 1fr 1fr and repeat(3, 1fr) describe exactly the same three equal columns, and a single 1fr column simply fills the container.
minmax() and fit-content()
minmax(100px, 1fr) gives a track a floor and a ceiling: never below 100px, otherwise a share of the free space. The two positions do not accept the same values. An fr value is only valid as the maximum: minmax(1fr, 300px) is invalid and the whole declaration is thrown away. As a minimum, auto means "the automatic minimum size of the items in that track", which is the behaviour behind the most common Grid bug on this page.
fit-content(300px) is the shorthand for "as wide as the content, but never past 300px". It takes a single length or percentage.
repeat(), auto-fill and auto-fit
repeat() writes a run of identical tracks once. repeat(12, 1fr) is twelve equal columns. The tool above collapses runs of two or more identical tracks into repeat() automatically; the checkbox beside the output turns that off if you would rather read every track.
The interesting form is the automatic one, which lets the browser decide how many tracks fit:
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 24px;
}This is the responsive card grid, and it needs no media query. auto-fill and auto-fit both repeat the track as many times as will fit. They differ in what happens to repetitions that end up with nothing in them: auto-fill keeps those empty tracks, so the remaining items stay at their natural size and leave a gap at the end; auto-fit collapses them to zero, along with their gaps, so the items that do exist stretch to fill the row.
Two rules catch people out. There may be only one automatic repeat in a track list, and the repeated track must be a fixed size: repeat(auto-fit, 1fr) is invalid, whilerepeat(auto-fit, minmax(240px, 1fr)) is valid because the minimum is a fixed length. That is why the minmax() in the snippet above is not optional.
gap#
gap is the space between tracks, and only between them: it adds nothing on the outside edges of the grid. It is the shorthand for row-gap and column-gap, in that order.
.grid-container {
row-gap: 24px;
column-gap: 16px;
/* The same pair, written once: row gap first. */
gap: 24px 16px;
/* One value sets both. */
gap: 16px;
}Percentages are allowed and resolve against the container's content box on that axis. The older names grid-gap, grid-row-gap and grid-column-gap still work as aliases in current browsers, but there is no reason to write them in new code.
Line numbers or named areas?#
Both generated outputs above describe the same layout. They differ in where the layout is written down.
Line-based placement
Each item names its own start and end lines. The container only declares the tracks.
.header {
grid-column: 1 / 4;
grid-row: 1 / 2;
}
.sidebar {
grid-column: 1 / 2;
grid-row: 2 / 3;
}
.main {
grid-column: 2 / 4;
grid-row: 2 / 3;
}span is the other way to write the same thing: grid-column: 1 / span 2 starts at line 1 and covers two tracks, and grid-column: span 2 on its own means "two tracks, wherever the auto-placement algorithm puts me". Spans are handy in a 12-column system, where you care about the width of a thing more than where it starts.
.page {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 24px;
}
/* Two thirds, then one third. */
.article { grid-column: span 8; }
.rail { grid-column: span 4; }
/* Or by line: start at 2, stop at the last line of the explicit grid. */
.full-width { grid-column: 2 / -2; }Named areas
grid-template-areas draws the layout as a picture in the container, and each item just claims a name.
.layout {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
gap: 16px;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }The rules the browser enforces here are strict, and the tool above respects all of them:
- Every string must hold the same number of cell names, otherwise the declaration is invalid.
- Every name must describe a single filled rectangle. Two separate blocks with the same name make the whole declaration invalid, which is why the tool refuses to let two items share a name.
- A period is a null cell:
.leaves that cell unnamed and empty. A run of periods such as...counts as one null cell, which is handy for keeping the columns lined up. - Names are identifiers, so
nonecannot be one: it is the keyword that means "no areas at all". The tool also rejectsauto,spanand the CSS-wide keywords. - Each named area quietly creates four named lines around it,
header-startandheader-endon both axes, which you can then use in line-based placement.
The payoff for the extra structure is rearranging. Because the whole layout lives in one property, a media query can redraw it without touching a single item:
@media (width < 48rem) {
.layout {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"aside"
"footer";
}
}Rough rule: use named areas for page-level layouts that change shape at breakpoints, and line numbers for component-level grids, spans in a column system, and anything generated from data where the names would be meaningless.
Browser support#
CSS Grid is safe to use without a fallback. It shipped unprefixed in Firefox 52 and Chrome 57 in March 2017, in Safari 10.1 later the same month, and in Edge 16 in October 2017; gapon a grid container arrived on the same schedule. MDN lists grid layout as Baseline "widely available", its highest support tier.
Subgrid, which lets a nested grid inherit its parent's tracks with grid-template-columns: subgrid, took longer: Firefox 71, Safari 16, then Chrome and Edge 117 in September 2023. It is now supported in every current browser, though it is younger than the rest of Grid.
Masonry layout, where items pack into the shortest column rather than a strict row, is still unsettled. The CSS Working Group has not agreed on whether it belongs inside grid as a masonry track value or in a display type of its own, and no syntax is supported across browsers yet. Do not ship it.
The specifications themselves are CSS Grid Layout Level 1 and Level 2, which adds subgrid. Both are Candidate Recommendation Drafts dated 26 March 2025, which is the stage a CSS feature usually stays at for years while it is already shipping everywhere.
Gotchas worth knowing#
An fr track that refuses to shrink
This is the one everybody meets. A grid item's automatic minimum size is its content, not zero, so a track sized 1fr will not go below the widest unbreakable thing inside it: a long URL, a wide table, a pre block. The row then overflows the container, and no amount of overflow: hidden on the item fixes the track itself.
/* A long unbreakable string pushes this track wider than its share. */
.grid-container {
grid-template-columns: 1fr 1fr;
}
/* The floor is lifted off the content, so the track can shrink. */
.grid-container {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}minmax(0, 1fr) sets the floor to zero explicitly, so the track is free to shrink and the content inside it wraps or scrolls instead. min-width: 0 on the item does the same job from the other side. Pick minmax(0, 1fr) from the suggestions in any track field above to try it.
Line 3 is not column 3
Lines sit between tracks, so the end line of an item is one higher than the last column it covers. A single-cell item at column 2 is grid-column: 2 / 3. The generator writes both lines out in full for exactly this reason.
Items overlap, quietly
Nothing stops two items claiming the same cells; they simply stack, painted in source order and reorderable with z-index. That is useful for a caption over an image, and it is a silent bug the rest of the time. The tool above refuses overlapping placements so the area map stays valid, but plain CSS will let you do it.
.hero-image {
grid-column: 1 / -1;
grid-row: 1 / 3;
}
.hero-title {
/* Same cells, declared later, so it paints on top. */
grid-column: 2 / 4;
grid-row: 2 / 3;
z-index: 1;
}Items you never placed
Any child beyond what your template covers still gets laid out: the browser creates implicit tracks for it, sized by grid-auto-rows and grid-auto-columns, which default to auto. If a layout grows a mysterious extra row, count your children against your template.
Alignment defaults
justify-items and align-items start at normal, which for grid items behaves as stretch: an item fills its area on both axes unless you say otherwise. justify-content and align-content, which decide what happens to space the tracks do not fill, start at normal too. For a grid that means stretch, but stretch only grows tracks sized auto: tracks with a size of their own are left alone and the spare space collects at the end. So a grid of fixed-width columns in a wider container sits at the left, not the centre.
Questions people ask#
Should I use Grid or Flexbox?
Grid when you care about both axes at once, such as a page shell, a dashboard or a card grid with aligned rows. Flexbox when the content decides the arrangement along one axis: a toolbar, a row of tags, anything that should wrap naturally. They compose well, and most real pages use a grid for the shell and flex inside the boxes. There is a Flexbox generator here too.
How do I make the grid responsive?
Three ways, in increasing order of control. repeat(auto-fit, minmax(240px, 1fr)) handles card grids with no media query at all. minmax() and fr on a fixed number of columns handles most component layouts. For a real change of shape, redraw grid-template-areas in a media query, which is where named areas earn their keep. Clamping the sizes themselves is a separate job: the clamp() calculator covers that.
Why is my grid one column wide?
Usually one of three things. The container does not have display: grid on it. The template property is misspelled or holds an invalid value, in which case the browser drops the whole declaration. Or the children are not direct children of the grid container: Grid only lays out immediate children, so a wrapper div in between makes the wrapper the only item.
Can I name grid lines as well as areas?
Yes. Square brackets in the template name a line, as in grid-template-columns: [full-start] 1fr [content-start] 600px [content-end] 1fr [full-end], and an item can then say grid-column: content-start / content-end. This generator writes numbered lines and area names, which covers the common cases; named lines are worth reaching for in a design system where the same names are used across many components.
Does the generated HTML have to look like that?
No. The markup is the simplest thing that matches the CSS: one container and one element per item, each carrying the class it is placed by. Use any elements you like, as long as they are direct children of the container. The classes are the part that matters.
How many columns and rows can I add?
Up to 12 of each in this tool, and up to 24 items. CSS itself has no such limit, and a 12-column template is already more than most layouts need. If you need more, the generated CSS is ordinary code: add tracks to it by hand.
What happens to my items when I remove a column?
Items that stretched past the new last line are clipped to it, and an item that would be left with no cells at all is removed. That keeps every item a valid rectangle inside the explicit grid, which is what both output modes need.
Related tools
Build a flex container by eye and copy the code: direction, wrapping, justify-content, align-items, align-content and gap, plus flex-grow, flex-shrink, flex-basis, align-self and order on any item, with only the properties you changed in the output.
Build linear, radial and conic CSS gradients visually: any angle, shape, size and centre, colour stops with their own position and opacity, repeating variants and an interpolation colour space, with the background declaration ready to copy.
Build a CSS box-shadow visually, with as many layers as you need: offsets, blur, spread, colour with opacity and inset, over a preview whose box colour, background and border radius you set yourself.
Work out a fluid clamp() declaration from a size at two viewport widths, with the slope and intercept shown, a live preview at any width, a type scale in custom properties and a check that the text still resizes.