← Front-End Developer — all chapters  ·  Kelick Tech Hub

Chapter 10 — Layout with Grid

Section 1 — What Grid is for

CSS Grid is a layout tool for two dimensions at once: rows and columns together. Where Flexbox arranges items along one line, Grid lets you draw a whole grid — a page with a header, sidebar, main area, and footer, or a gallery where rows and columns line up with each other. If the layout needs both directions to stay aligned at the same time, Grid is the tool. That two-dimensional nature is the fundamental difference between the two layout systems, and it determines which one you should reach for. A row of cards that wraps is a Flexbox job, because each row is independent. A page where the sidebar must line up with the main content across several rows is a Grid job, because the rows and columns are defined together and items are placed into the cells they create.

Grid's power comes from the fact that the container defines the structure before the items arrive. You describe the columns and rows — their number, their sizes, their names — and then you place items into that structure, either by letting the browser fill it automatically or by telling each item exactly where to go. That is a different way of thinking from Flexbox, where the container reacts to its items. In Grid, the structure comes first, and the items conform to it. The practical benefit is alignment: items in different rows share the same column widths, because those widths belong to the grid, not to the individual items. A dashboard with panels in three columns will keep those columns aligned even when the panels contain wildly different amounts of content, because the columns are properties of the container, not consequences of the content.

It is worth being clear about what Grid is not. It is not a replacement for Flexbox, and it is not a page framework. It is a CSS layout mode, like Flexbox and block layout, and it works alongside them: Grid for the two-dimensional structure, Flexbox for the one-dimensional arrangement inside the grid's areas, block layout for documents. Most real pages combine all three, with Grid defining the page skeleton, Flexbox arranging the contents of each region, and block layout handling the text within. Learning Grid does not mean abandoning Flexbox; it means adding the second dimension to your toolkit, so that layouts which used to require fragile workarounds — equal columns across rows, aligned sidebars, overlapping regions — become straightforward declarations.

Try a small experiment to feel the difference. Build a container with six boxes and give it three equal columns with Grid, then build the same six boxes as a wrapping flex row. Resize the window and compare: the Grid version keeps three columns of equal width until you change the definition, while the flex version reflows its rows independently. Then put a wide box spanning two columns into each version and compare again: in Grid, the spanning is a single placement declaration; in Flexbox, it requires width calculations that break when the row wraps. That experiment captures the essence of this chapter: Grid is for layouts where the structure matters as much as the items, and where rows and columns must agree with each other.

.page {
  display: grid;
}

Check: What makes Grid different from Flexbox in terms of dimensions?

Section 2 — The grid container and its items

Setting display: grid on an element makes it a grid container, and its direct children become grid items. As with Flexbox, only direct children count: if a container holds a section, and that section holds articles, the section is the grid item and the articles are not, until the section itself becomes a grid container. Grid works one level at a time, just as Flexbox does, and the same discipline applies: each container manages its own direct children, and deeper arrangement requires deeper containers. Keeping that rule in mind prevents the most common Grid confusion, which is setting placement properties on elements that are not grid items and wondering why nothing moves.

The division of labour between container and items mirrors Flexbox but with a two-dimensional twist. The container owns the structure: the columns, the rows, the gaps, the named areas, and the default alignment of items within their cells. The items own their placement and their individual alignment: which columns and rows they span, which named area they claim, and whether they align differently from the container's default. Most Grid properties therefore fall clearly on one side or the other, and the same diagnostic question from Chapter 9 applies: am I describing the whole grid, or one item in it? The answer tells you whether the declaration belongs on the container or on the item, and getting that right is half of Grid debugging.

Grid items behave differently from ordinary blocks in ways worth knowing. Like flex items, they stop taking the full width of their parent and stop stacking by the ordinary block rules; instead, they are placed into cells by the container. Unlike flex items, they are placed in two dimensions, so an item can span several rows as well as several columns, and it can overlap other items if placed into the same cells — Grid does not prevent overlap, it simply places items where told, and overlapping items stack according to their document order and z-index, a topic Chapter 11 returns to. An item's size is also determined by its cell or cells, stretched by default to fill them, which is why grid items so often appear to "fill" their area without any sizing declarations of their own.

The example below shows the minimal structure: a container with three children, each of which will become a grid item. With only display: grid and no column or row definitions, they will stack in a single column, which looks unremarkable. The interest begins when you define columns, and the rest of this chapter is about those definitions and the placements they make possible. Type the example, add a column definition, and watch the three children rearrange into a row — that single change, from stacking to columns, is the moment Grid's structure-first approach becomes visible, and it is the foundation for everything that follows.

<div class="page">
  <header>Header</header>
  <main>Main content</main>
  <footer>Footer</footer>
</div>

Check: Which elements become grid items when display: grid is set?

Section 3 — Turning Grid on

display: grid alone gives you a single-column grid: items stack like blocks, each in its own row, each stretched to the container's width. That looks unremarkable until you define columns, which is where Grid starts to show its power. The container is the control centre — almost all Grid properties live there — and the single-column default is simply the grid with one column and as many rows as there are items, each row sized to its content. Understanding that default matters, because it means a grid container without definitions is not "broken" or "inactive": it is a working grid with the simplest possible structure, and every definition you add refines that structure rather than switching something on.

There is a sibling value, display: inline-grid, which creates a grid container that behaves like an inline element on the outside — sitting within a line of text or beside other inline content — while arranging its children as a grid on the inside. Ordinary display: grid makes a block-level container, which is what nearly every page layout wants: a full-width structure that other content flows around as a block. inline-grid is for the rarer cases where a small grid should sit inline, such as a set of labelled values inside a paragraph or a miniature table-like arrangement within a sentence. Knowing it exists is enough for now; you will use block-level grids for the overwhelming majority of your work, and inline-grid will be there for the occasional inline case.

One detail that surprises beginners is that display: grid does not change the items themselves into anything visible on its own. The items are still the same elements, with the same content and the same basic styling; only their arrangement has changed, and with a single column, that arrangement looks like ordinary stacking. The change becomes visible when you define columns, set gaps, or place items, because those are the operations that use the grid structure. If your grid seems to do nothing, the first check is whether you have actually defined any structure beyond the default — a grid with one column and no gaps is indistinguishable from block layout, and that is expected, not a bug.

A useful habit when starting a Grid layout is to build it in stages: first the container and its items, then the column definition, then the gaps, then the placement of special items. At each stage, reload and look. That staged approach makes it obvious which declaration produced which change, and it builds the mental model of structure-first layout far better than writing the whole grid at once and trying to debug it as a unit. The example below is the smallest possible grid; type it, open it, and then add grid-template-columns: 1fr 1fr and watch the items rearrange. That two-step experiment is the whole of Grid in miniature: structure defined, items placed, layout transformed.

.layout {
  display: grid;
}

Check: With only display: grid and no other grid properties, how do items arrange themselves?

Section 4 — Defining columns

grid-template-columns defines the columns of the grid, one size per column. grid-template-columns: 200px 1fr makes a 200-pixel column and a second column that takes the rest of the space. The number of sizes you list is the number of columns you get: two sizes, two columns; four sizes, four columns. Each size can be a fixed length, a fraction, a percentage, an automatic size, or a minmax range, and the columns can mix these freely — a fixed sidebar, a flexible main area, and a narrow gutter column are all just entries in the same list. That list is the heart of most Grid layouts, because it states the horizontal structure in one readable line.

The sizes are resolved in a specific order that is worth understanding, because it explains how mixed lists behave. Fixed sizes are taken first, then content-based sizes, and the remaining free space is shared among the fractional tracks according to their fr values. So in 200px 1fr 2fr, the 200 pixels are removed first, and the rest is split into three parts, one going to the middle column and two to the last. In 100px auto 1fr, the fixed column takes 100 pixels, the automatic column takes whatever its content needs, and the fractional column takes everything left. That resolution order is why fractional columns are so useful: they absorb whatever space the fixed and content columns leave, which means the grid always fills its container exactly, without leftover space or overflow, as long as the fixed and content columns fit.

Choosing column sizes is a design decision that should reflect the content's needs. A sidebar that holds navigation wants a fixed size, because navigation labels should not stretch or shrink with the window. A main content area wants a fraction, because it should grow with the screen. A gallery wants equal fractions, because its columns should match. A form with labels and inputs might want a fixed label column and a flexible input column. In each case, the template-columns list is a direct statement of the design's intent, readable by anyone who knows the units — and that readability is one of Grid's great strengths compared with older techniques, where the same structure required nested wrappers, floats, and width calculations scattered across several rules.

It is also worth noting that you do not have to define every column explicitly. Columns you do not define are created implicitly when items need them, as Section 15 explains, and they size themselves automatically. But relying on implicit columns for your main structure is a mistake: implicit tracks are a safety net for extra items, not a design tool, and their automatic sizing can produce uneven results. Define the columns you intend, explicitly, in the template, and let implicit tracks handle only the unexpected overflow. A grid whose template states its full structure is a grid whose behaviour you can predict, and predictability is the reason to use Grid in the first place.

.layout {
  display: grid;
  grid-template-columns: 200px 1fr;
}

Check: How many columns does grid-template-columns: 200px 1fr create?

Section 5 — Defining rows

grid-template-rows works the same way for rows: one size per row, with the same range of units available. Rows are often left to size themselves to their content, so you will define columns more often than rows, but explicit rows are useful when you want a header of a fixed height, rows that share the space evenly, or a layout whose vertical rhythm matters as much as its horizontal structure. A page grid with a header row of 80 pixels, a flexible content row, and a footer row of 60 pixels is a direct statement of the page's vertical shape, just as the column template states its horizontal shape, and the two templates together define the complete grid.

The asymmetry between rows and columns in practice deserves explanation. Web pages scroll vertically, so their total height is usually unknown — content determines how tall a page is, and the page grows as content is added. Fixed row heights therefore fight against the natural growth of content, and a row with a fixed height that its content exceeds will either clip the content or overflow, depending on other properties. Columns, by contrast, live within a known width — the viewport or the container — so fixed column widths are safe in a way fixed row heights often are not. That is why most grids define their columns explicitly and let their rows size to content, using grid-auto-rows for the implicit rows that content creates, rather than fixing every row in advance. The template-rows property is there when you need it, but needing it is less common than needing template-columns.

When you do define rows, the same resolution order applies as for columns: fixed sizes first, content sizes next, fractions sharing the remainder. A grid with grid-template-rows: 80px 1fr 60px inside a container of a known height — say, a full-viewport dashboard — gives the header 80 pixels, the footer 60, and the middle row everything in between. That pattern is the vertical equivalent of the sidebar layout, and it is the basis of application-style layouts where the header and footer are fixed and the content area fills the rest. The key requirement is a known container height: without it, the fractional row has no free space to share, and it collapses to its content size, just as a fractional column collapses without a known container width. Fractions need a definite space to divide, in either dimension.

A final point about rows is their relationship with content that spans them. An item that spans two rows occupies both, and the rows' sizes are determined by the template and by the content of all items in those rows, not just the spanning item. That interplay can produce surprising row heights when a tall spanning item forces its rows taller than their other contents need, but it is also what makes spanning useful: a sidebar that spans the content rows of a page grid stretches to match their combined height, giving the classic full-height sidebar without any height calculations. Rows and spanning work together, and understanding that partnership is essential for the page layouts in the later sections of this chapter.

.layout {
  display: grid;
  grid-template-rows: 80px 1fr 60px;
}

Check: What does grid-template-rows: 80px 1fr 60px describe?

Section 6 — The fr unitThe `fr` unit means "a fraction of the free space". In `1fr 2fr`, the second column gets twice as much of the leftover space as the first, after fixed-size tracks are taken out. It is like flex-grow for grid tracks, and it is the reason Grid layouts adapt so smoothly to different screen widths: as the container grows or shrinks, the fractional tracks absorb the change proportionally, while the fixed tracks stay put. A sidebar of 200 pixels beside a main area of 1fr is the simplest expression of this: the sidebar never moves, the main area takes everything else, and the layout stays correct at every width without a single media query.

The calculation behind fr is worth walking through, because it explains several behaviours that otherwise look arbitrary. First, the browser removes the space taken by fixed tracks, gaps, and content-sized tracks that have a definite size. What remains is the free space. Then it divides that free space into shares according to the fr values: 1fr 1fr divides it into two equal shares, 1fr 2fr into three shares with the second track taking two, 1fr 3fr into four shares with the second taking three. The shares are then assigned to the tracks. Note that the division is of the free space only, not of the whole container — a common misunderstanding that leads people to expect 1fr 2fr to produce columns in a one-to-two ratio of the total width, when in fact the ratio applies only after fixed tracks are removed. With no fixed tracks, the two ratios coincide, which is why equal-fraction galleries look exactly proportional, but with a fixed sidebar present, the proportions are of the remainder.

Fractions also interact with content minimum sizes in a way that can surprise you. A fractional track will not shrink below the minimum size of its content by default, just as a flex item will not shrink below its content floor. A 1fr column containing a wide image or a long unbroken word may therefore end up wider than its share, pushing the other tracks narrower than their shares, or overflowing the container if the content simply cannot fit. The remedy is the same family of techniques as in Flexbox: constrain the content, allow it to wrap, or use minmax(0, 1fr) instead of plain 1fr, which sets the track's minimum to zero and lets it shrink below its content size. That minmax(0, 1fr) pattern appears so often in real Grid code that it is worth recognising on sight: it means "a fractional track that is allowed to shrink fully", and it is the standard answer to a fractional column that refuses to narrow.

.layout {
  display: grid;
  grid-template-columns: 1fr 2fr;
}

Check: After a fixed 200px column is taken out, how is the remaining space shared by 1fr and 2fr?

Section 7 — repeat()

When several tracks share a size, repeat() saves repetition: repeat(3, 1fr) means three equal columns, the same as writing 1fr 1fr 1fr, but shorter and clearer, especially as the count grows. repeat(12, 1fr) is a twelve-column grid in a single expression, where writing twelve fractions by hand would be tedious and error-prone. The function takes a count and a track list, and it expands to the track list repeated that many times. The track list can itself contain several sizes, so repeat(2, 100px 1fr) expands to four columns alternating between 100 pixels and a fraction — a pattern that would be awkward to express without the function, and that becomes readable with it.

Repeat becomes genuinely powerful when combined with the automatic repetition keywords, auto-fit and auto-fill, covered in Section 19. Those keywords replace the count with a calculation: "as many as fit", based on the container's width and the track's minimum size. But even with a fixed count, repeat improves maintainability: changing a gallery from four columns to three is a one-number change, from repeat(4, 1fr) to repeat(3, 1fr), rather than an edit to a list of four values where one might be missed. That kind of single-point change is the difference between a stylesheet that is easy to adjust and one that invites errors, and it is a good habit to prefer repeat whenever two or more tracks share a size, even for small counts where the saving seems trivial.

There is a subtlety about what can be repeated. The track list inside repeat can contain fixed sizes, fractions, percentages, minmax ranges, and content sizes, but it cannot contain every kind of track in every context — in particular, the automatic repetition keywords have their own restrictions, which Section 19 covers. With a fixed count, the restrictions are few, and you can repeat almost any combination. A pattern like repeat(3, 1fr 2fr) gives six columns in three pairs, each pair in a one-to-two ratio, which is a layout that would be genuinely confusing to write out longhand but is clear as a repeat expression. The function is, in effect, a small loop in your stylesheet, and like any loop, it earns its keep by replacing repetition with a rule.

It is also worth noting that repeat is not just about saving keystrokes. It communicates intent: "these columns are the same, by design" is a statement about the layout, and expressing it as a repeat makes that statement visible to the next developer, who will then know that changing one column means changing them all. A longhand list of identical values does not carry that message as clearly — it could be a coincidence, or a set of values that happen to match today but are meant to diverge tomorrow. Using repeat for genuinely uniform tracks, and longhand for tracks that are individually chosen, makes the stylesheet's intentions legible, which is a form of documentation that never goes out of date, because it is the code itself.

.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

Check: What does repeat(4, 1fr) expand to?

Section 8 — gap in Grid`gap` sets the gutters between rows and columns, with `row-gap` and `column-gap` for setting them separately. Grid's gap is one of its great conveniences: the gutters are part of the grid itself, so you never need margins on items or tricks to remove the margin from the last item in a row. The gaps are subtracted from the container's space before tracks are sized, so a grid with three 1fr columns and a 16-pixel gap divides the space minus two gaps into three equal shares, not the full space — the columns come out slightly narrower than they would without gaps, and the total, including gaps, exactly fills the container. That arithmetic is handled by the browser, and it is always correct, which is the point: you state the gutter size once, and the grid accounts for it everywhere.

The difference between gap and margin, already covered for Flexbox in Chapter 9, applies with even more force in Grid, because Grid's two-dimensional structure makes margin-based gutters especially fragile. A margin on a grid item adds to the item's size within its cell, potentially overflowing the cell, and margins at the edges of the grid add space where the design usually wants none. Gap, by contrast, exists only between tracks, never at the outer edges, and never inside a cell — it is space between the structure, not space belonging to any item. For a gallery, a dashboard, or a page layout, that distinction produces cleaner results with less code, and it is one of the reasons Grid layouts tend to look precise: the gutters are uniform by construction, not by careful margin management.

Gap also interacts with spanning and placement in a way that is easy to overlook. When an item spans two columns, it spans the columns and the gap between them — the gap becomes part of the spanned area, so the item's width includes it. That is the correct behaviour, because the gap is space between tracks, and spanning across tracks necessarily crosses the gap, but it means a spanned item is slightly wider than the sum of its columns alone. Similarly, when items are placed with gaps between them, the visual rhythm of the layout comes from the gaps as much as from the tracks, and changing the gap is often the quickest way to change the feel of a layout from dense to airy. Treat gap as a first-class design decision, not an afterthought, and set it deliberately for every grid you build.

.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

Gaps are also subtracted before fractional tracks are sized, so three 1fr columns with 16-pixel gaps share the container width minus 32 pixels, not the full width. That is a detail the browser handles for you, but it explains why gapped columns look slightly narrower than gapless ones.

Check: Why is gap tidier than margins for grid gutters?

Section 9 — Grid vocabulary: tracks, cells, lines and areasA track is one row or column of the grid. A cell is where one row and one column cross — the smallest unit of the grid, the rectangle formed by one row track and one column track. A line is the boundary between tracks, numbered from 1 at the start, with negative numbers counting from the end. An area is a rectangle of cells given a name, formed by merging adjacent cells under one name in a template-areas drawing. These four words come up constantly in Grid, in documentation, in developer tools, and in conversation with other developers, so it is worth learning them now rather than guessing later — and it is worth learning them precisely, because Grid's properties are named after them, and a property makes sense only when its vocabulary does.

The numbering of lines deserves a careful look, because it is the source of the most common off-by-one errors in Grid. A grid with three columns has four column lines: line 1 before the first column, line 2 between the first and second, line 3 between the second and third, and line 4 after the third. An item spanning all three columns therefore runs from line 1 to line 4, not from line 1 to line 3, and writing grid-column: 1 / 3 covers only the first two columns. The rule is simple once stated — there is always one more line than there are tracks — but it contradicts the intuition carried over from counting columns, and it catches nearly every beginner at least once. Negative lines follow the same logic from the other end: line -1 is the last line, line -2 the second-to-last, and so on, so grid-column: 1 / -1 always spans the full width regardless of how many columns there are.

Cells and areas are the two ways of thinking about where an item goes. A cell is the address of a single position — row 2, column 3 — while an area is a named region that may cover several cells, such as a header spanning all columns or a sidebar spanning several rows. Placement by lines addresses cells and rectangles of cells directly, while placement by area names addresses the named regions. Both are valid, and the choice between them is a matter of which mental model suits the layout: area names for page structures that read like a sketch, line numbers for galleries and dashboards where the geometry is the point. Developer tools reinforce this vocabulary by drawing the lines, numbering them, and labelling the areas, so learning the words also makes the tools more useful.

/* 3 columns = 3 tracks, 4 lines, 3 cells per row */
.layout {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

Check: What is a grid track, and what is a grid cell?

Section 10 — grid-template-areas`grid-template-areas` lets you draw the layout as text. Each quoted string is a row, and each word in the string is a cell. Repeating a word across cells merges them into one area, so a header word repeated across all columns becomes a header area spanning the full width, and a sidebar word repeated down several rows becomes a sidebar area spanning those rows. The result reads almost like a sketch of the page, which makes it the friendliest way to build a page layout, and the easiest layout for another developer to understand at a glance — the structure is visible in the stylesheet as a picture, not hidden in a set of line numbers.

The rules for a valid areas drawing are simple but strict, and they are worth knowing because invalid drawings are silently ignored, which can be confusing. Every row must have the same number of cells, so every string must contain the same number of words. An area name must form a rectangle: it cannot be L-shaped or disconnected, because a grid area is by definition a rectangle of cells, and a non-rectangular shape cannot be an area. A cell can also be left empty, using a full stop as a placeholder, which creates an unnamed cell that no item claims — useful for layouts with a deliberate hole, such as a page where the sidebar does not extend into the footer row. And area names are custom identifiers, so they must follow the rules for CSS identifiers: they cannot start with a digit, and they are case-sensitive, so Header and header are different areas.

The power of areas becomes clear when a layout changes. Suppose a page design moves the sidebar from the left to the right at a certain width, or stacks all regions into a single column on a phone. With areas, that change is a change to the drawing alone: rewrite the strings, and every item follows its area name to its new position, without any item's placement declaration changing. That separation — items claim names, the template defines where names sit — is what makes areas so maintainable for page layouts. The items do not know or care where their area is; they only know its name, and the template is the single place where the geometry lives. A responsive page built with areas therefore has one template per breakpoint, each a readable sketch, and no item-level changes at all.

.page {
  display: grid;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
}

Area names also follow the rules for CSS identifiers, so they cannot start with a digit, and they are case-sensitive — Header and header are different areas. Consistent lower-case names across a project prevent a whole class of silent mismatches where an item claims a name the drawing never defined.

Check: In a template-areas drawing, what does repeating a word across two cells do?

Section 11 — Placing items by area nameOnce areas are named, each item claims one with `grid-area`. The header takes the header area, the sidebar takes the sidebar area, and the browser works out the spanning for you — an item claiming an area that spans three columns and two rows automatically occupies that whole rectangle, without you calculating any line numbers. If you later rearrange the drawing, the items follow their area names to their new positions, which is the maintainability benefit from Section 10 in action: the item declarations never change, only the template does, and the layout transforms. That stability is valuable in a real project, where page layouts are restyled more often than their content is restructured.

An item that claims an area it does not fit — because the area is smaller than the item's content, for example — behaves like any grid item in a too-small cell: its content may overflow, or the track may grow to accommodate it, depending on the track sizing. Areas do not clip or resize content by themselves; they are placements, not containers with overflow rules. If a sidebar area is 200 pixels wide and its content needs 300, the sidebar's content will overflow or force the track wider, according to the same content-minimum rules that govern all grid tracks. The fix is the same as elsewhere: constrain the content, allow wrapping, or widen the track. Areas make placement readable, but they do not suspend the sizing rules, and a layout that ignores sizing will break in areas just as it breaks in line-based grids.

There is also the question of what happens to items that claim no area. An item without a grid-area declaration is placed automatically, into the first available cell in the automatic placement order, which may be inside or outside the named areas depending on which cells are already claimed. That can be useful — a page grid with named regions for the header, sidebar, and footer, plus automatically placed content sections that flow into the remaining cells — but it can also be surprising if you expected an unclaimed item to sit somewhere specific. The safe habit is to claim an area for every item in an areas-based layout, so that no item's position is left to automatic placement by accident. Explicit placement for every region, automatic placement for none, is the recipe for a page layout that behaves exactly as its drawing promises.

.page-header {
  grid-area: header;
}

.page-sidebar {
  grid-area: sidebar;
}

If two items claim the same area, they overlap, stacking in document order. That is a useful technique for a caption over an image, covered for Grid overlap in Section 31, but a mistake if it happens accidentally, so check that each area name is claimed by exactly one item unless overlap is intended.

Check: How does an item claim a named area?

Section 12 — Placing items by line numbersInstead of names, you can place an item by the grid lines it spans: `grid-column: 1 / 3` means "start at line 1, end at line 3", covering two columns. Lines are numbered from 1 at the start, and negative numbers count from the end, so `-1` is the last line, and `grid-column: 1 / -1` spans the full width of any grid, regardless of how many columns it has. That negative-number trick is one of the most useful single declarations in Grid, because it expresses "full width" without knowing or caring about the column count, and it keeps working when columns are added or removed. A header, a footer, or a full-width banner in a grid can all use it, and the declaration never needs updating when the grid's structure changes.

Line placement can be partial: you can set only the start line, only the end line, or use a span, and the browser will work out the rest from the item's size and the automatic placement rules. grid-column: 2 places the item starting at line 2, in a single column, unless its span says otherwise. grid-column: 2 / span 2 starts at line 2 and covers two columns. Those partial forms are useful when you know where an item should start but want its width to be determined by its span or by its content, rather than by a fixed end line. They also combine well with automatic placement: an item with only a column start will be placed in the first available row at that column, which is how dashboards often position widgets that should sit in a particular column but flow down the rows as they are added.

The choice between line numbers and area names is, as Section 9 noted, a choice of mental model. Line numbers suit layouts where the geometry is the point — galleries, dashboards, and data displays where an item's position is a matter of columns and rows rather than of named regions. Area names suit page skeletons where the regions have meaningful names and the geometry may change. Many real layouts use both: areas for the page skeleton, lines for the widgets inside a dashboard region. The two systems coexist without conflict, because they are just two ways of addressing the same grid, and an item placed by lines can sit beside an item placed by an area name in the same container. Learning both, and choosing per layout rather than per habit, is the mark of a developer who uses Grid fluently rather than by rote.

.wide-item {
  grid-column: 1 / -1;
}

Line placement also has a useful trick for centring a single item across a grid. Placing an item from the first line to the last line in both directions makes it fill the whole grid, and then aligning it within that area centres it in the full container. That technique is sometimes used for a loading overlay or a centred message over a grid, because it keeps the item tied to the grid structure instead of using a separate positioning scheme.

Check: In a three-column grid, what does grid-column: 1 / -1 cover?

Section 13 — span

span says how many tracks an item covers, without naming the end line: grid-column: span 2 makes an item two columns wide wherever it lands. It is handy when you care about the size of an item but not its exact position — a featured card that is always double-width, for example, or a dashboard widget that always occupies two columns, wherever the automatic placement puts it. The item's start is determined by placement rules — either an explicit start line, or the first available position — and its end is the start plus the span, so the item's width is fixed while its position remains flexible. That combination of fixed size and flexible position is exactly what automatic-placement layouts need.

Span works on both axes: grid-row: span 2 makes an item two rows tall, and the two can combine, so grid-column: span 2 with grid-row: span 2 makes an item twice as wide and twice as tall as a normal cell — a featured tile in a gallery, or a large widget in a dashboard. The spanning interacts with the track sizes in the obvious way: the item occupies its spanned tracks plus the gaps between them, so a two-column span in a grid with 16-pixel gaps is two columns plus 16 pixels wide. It also interacts with the content of the spanned area: the item stretches to fill its spanned rectangle by default, just as a single-cell item stretches to fill its cell, unless its alignment says otherwise.

A subtlety of span is what happens when the span does not fit at the item's starting position. If an item with span 2 is placed by automatic placement and only one column remains in the current row, the browser will move it to the next row where two columns are available, rather than splitting it or squeezing it into one column. That behaviour keeps items whole, but it can leave holes in the grid — empty cells at the end of a row where a wide item did not fit. The dense packing mode, covered in Section 38, can fill those holes by pulling later, smaller items forward, at the cost of reordering the visual sequence. The default, sparse packing leaves the holes, preserving the document order at the cost of some empty space. Which is better depends on whether the grid's visual order or its density matters more, and the choice is a design decision, not a technical default to accept blindly.

Span is also the mechanism behind many gallery patterns. A photo gallery where most images occupy one cell and a few featured images span two columns and two rows — the classic masonry-like look, without actual masonry — is built with spans plus automatic placement. The featured images get their spans, the ordinary images get none, and the browser flows them into the grid, leaving holes where a featured image did not fit, or filling them with dense packing. That pattern is simple to write and striking to look at, which is why it appears in so many portfolio and photography sites, and it is a good example of how a single small property, combined with automatic placement, can produce a sophisticated-looking layout with very little code.

.featured {
  grid-column: span 2;
}

Check: What does grid-column: span 2 mean?

Section 14 — grid-column and grid-row shorthand

grid-column is shorthand for the start and end lines of a column placement, and grid-row does the same for rows. grid-column: 2 / 4 and grid-row: 1 / 3 together place an item in a precise rectangle: starting at column line 2, ending at column line 4, starting at row line 1, ending at row line 3. You will write these two more than any other placement properties, because they are the direct, line-based way to say "put this item here, this big". Their longhand forms — grid-column-start, grid-column-end, grid-row-start, grid-row-end — exist for cases where you need to set one end without disturbing the other, such as in a media query that changes only an item's end line, but the shorthand is the standard form for ordinary placement.

The values each side of the slash can take are the same family: a line number, a negative line number, a named line, or a span. So grid-column: 2 / span 2 starts at line 2 and covers two columns; grid-column: span 2 / -1 ends at the last line and covers two columns backwards from it; and grid-column: sidebar-start / sidebar-end uses named lines, covered in Section 33. That flexibility means the shorthand can express start-anchored, end-anchored, and span-based placements in a single, consistent form, and learning to read it fluently — start, slash, end, with each side being a number, a name, or a span — is essential for reading other people's Grid code, where these forms appear constantly and often mixed within the same stylesheet.

A common error with the shorthand is reversing the order or forgetting the slash. grid-column: 2 4 without a slash is invalid and will be ignored, leaving the item automatically placed — a silent failure that can be confusing, because the item appears somewhere plausible but not where intended, and there is no error message. Similarly, grid-column: 4 / 2, with the end before the start, is invalid in most cases, though the specification allows the browser to swap them in some situations, which makes the behaviour inconsistent across browsers and best avoided. Writing the shorthand carefully — start, slash, end, with start before end — and checking the result in the browser is the practical defence, along with developer tools, which show the resolved placement and make a misplaced item immediately visible.

Placement with both shorthand properties together is how you build the precise, dashboard-style layouts that Grid is famous for: a widget at columns 1 to 3 and rows 1 to 2, a chart at columns 3 to 5 and rows 1 to 4, a list at columns 1 to 2 and rows 2 to 4. Each widget gets two declarations, and the grid assembles them into a coherent dashboard, with gaps between them and alignment within their rectangles. When a widget needs to move, you change its two numbers; when the grid needs more columns, you change the template and the widgets that should expand get new end lines. That directness — two numbers per axis, no wrappers, no calculations — is why line-based Grid placement feels so much cleaner than the float and absolute-positioning techniques it replaced for dashboard layouts.

.hero {
  grid-column: 2 / 4;
  grid-row: 1 / 3;
}

Check: What rectangle does grid-column: 2 / 4 and grid-row: 1 / 3 describe?

Section 15 — Implicit tracksIf you place more items than your explicit grid has room for, Grid quietly creates extra rows or columns to hold them. These are implicit tracks. They exist so a grid never simply drops an item — every item gets a cell, even if the template did not provide one — but because you did not define them, they size themselves automatically, which may not be what you wanted. A three-column gallery with ten items, for example, has an explicit structure of three columns and as many rows as needed: the rows beyond the first are implicit if you did not define them in the template, and they will size to their content by default, which for a gallery of similar cards usually looks fine, but for a dashboard of varied widgets can produce uneven row heights.

Implicit tracks are not a flaw in the grid; they are a deliberate safety net, and they are essential for any grid whose item count is not known in advance. A blog index, a product listing, a search results page — all of these have a variable number of items, and a grid that required an exact row count in advance would be useless for them. The implicit mechanism lets you define the columns explicitly, which is the structure you care about, and let the rows grow as needed, which is the dimension that varies. The key is to control the implicit tracks' sizing with grid-auto-rows and grid-auto-columns, covered next, so that the safety net produces consistent results rather than content-sized accidents. An explicit template plus controlled implicit sizing is the standard recipe for a dynamic grid.

It is also worth knowing that explicitly placed items can create implicit tracks too. If you place an item at column line 5 in a three-column grid, the grid creates a fourth column to accommodate it, because the item's placement demands a track the template did not define. That can be a useful technique — a deliberately placed item extending the grid — but it is more often an accident, caused by a miscounted line number, and the resulting extra column can distort the whole layout. When a grid suddenly has an unexpected column or row, check whether an item's placement exceeds the template before assuming the template is wrong. Implicit tracks are the grid's way of honouring every placement, even the mistaken ones, and that honouring is both their virtue and their capacity to surprise.

/* Five items in a 2-column explicit grid: a third row is created implicitly. */
.gallery {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

Explicitly placed items can also create implicit tracks, as when an item is placed at a line beyond the template. An unexpected extra row or column is therefore often a misplaced item rather than a template error, and checking placements before rewriting the template saves time.

Check: What happens when there are more items than the explicit grid has cells for?

Section 16 — Sizing implicit tracks

grid-auto-rows and grid-auto-columns set the size of those automatic tracks. Setting grid-auto-rows: 200px means every implicit row is 200 pixels tall, so a gallery that grows with more items keeps a consistent row height instead of each new row sizing to its own content. That consistency is the main reason to set these properties: without them, implicit rows size to their content, and a gallery where one row holds tall cards and the next holds short ones will have visibly uneven rows, even though the columns are perfectly regular. A single grid-auto-rows declaration imposes order on the growing dimension, and it is one of the first properties to set on any dynamic grid.

The values available for implicit sizing are the same as for explicit tracks: fixed lengths, fractions, minmax ranges, and content sizes. A common pattern is grid-auto-rows: minmax(200px, auto), which means "at least 200 pixels tall, taller if the content needs it" — a floor that guarantees consistency without clipping content that exceeds it. Another is a fixed height for a uniform gallery, where every card is designed to fit the same height and overflow is handled inside the card. Fractions in implicit rows are trickier, because implicit rows are created as needed and there may be no definite container height to divide — a fractional implicit row in a container of indefinite height behaves like an automatic row, sizing to its content, which is a reasonable fallback but not the equal sharing you might have expected. For that reason, fixed and minmax values are the usual choices for implicit rows, with fractions reserved for explicit rows inside a container of known height.

Implicit columns, set by grid-auto-columns, follow the same logic in the horizontal direction, and they matter most for column-flow grids, where new columns are created as items are added. A horizontal scrolling gallery with column flow might set grid-auto-columns: 280px, so every new column is a 280-pixel card width, and the grid grows sideways in regular steps. Without that declaration, each new column would size to its content, producing a ragged horizontal strip. The symmetry between the two properties is worth noting: auto-rows govern growth in the row-flow direction, auto-columns govern growth in the column-flow direction, and in a given grid, usually only one of them is actively creating tracks, because the flow is either row-wise or column-wise. Setting both is harmless and can be good defensive practice, but understanding which one is doing the work keeps the stylesheet's intentions clear.

A practical guideline is to always pair an explicit column template with an explicit auto-rows value when building a gallery or listing. The columns define the horizontal structure, the auto-rows define the vertical rhythm, and together they guarantee that the grid looks regular no matter how many items arrive. That pair — template-columns plus auto-rows — is the skeleton of most dynamic grids, and it appears in countless real stylesheets in exactly that form. When a dynamic grid looks uneven, the first check is whether auto-rows is set, because its absence is the most common cause of ragged implicit rows, and its presence is the simplest fix.

.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 200px;
}

Check: Which property controls the height of automatically created rows?

Section 17 — grid-auto-flow

By default, Grid fills row by row: each new item goes in the next cell along the row, and when a row is full, it drops to the next row. grid-auto-flow: column fills column by column instead: each new item goes in the next cell down the column, and when a column is full, it moves to the next column. The default suits most layouts, because pages are read row by row and scroll vertically, so filling rows matches both reading order and scrolling direction. Column flow suits cases where items should stack down one column before starting the next — a multi-column list that reads down the first column, a horizontal layout where items accumulate sideways, or a dashboard where widgets fill a column before spilling into the next.

The flow also determines which implicit tracks are created, as Section 15 noted: row flow creates rows, column flow creates columns. That linkage means choosing a flow is also choosing a growth direction, and the choice should reflect how the layout should grow when more items arrive. A product listing that grows downwards wants row flow. A timeline that grows sideways wants column flow. A set of widgets in a fixed-height panel, where new widgets should start a new column rather than extending the panel's height, wants column flow with a fixed row template. In each case, the flow is not an abstract setting but a statement about the layout's growth, and stating it explicitly — even when it is the default — can make the intention clearer to a future reader.

There is a third value family for auto-flow that combines flow with packing: row dense and column dense, which add the dense packing behaviour covered in Section 38 to the respective flow direction. Dense packing fills holes left by spanning items by pulling later items forward, which increases density at the cost of visual order. The plain row and column values use sparse packing, leaving holes to preserve order. The distinction is important enough to have its own section, but it belongs to the flow property, so it is worth knowing here that flow and packing are set together in one declaration, and that grid-auto-flow: row dense is a single statement combining both decisions. Most grids use plain row, and dense is reserved for galleries where density matters more than order.

It is also worth knowing what auto-flow does not do: it does not affect explicitly placed items. An item with a grid-column and grid-row declaration goes where it is told, regardless of the flow, and the flow only governs items without explicit placement — the ones that fill in around the placed items. That means a grid can mix the two: a dashboard with a few explicitly placed hero widgets and a flow of smaller widgets filling the remaining cells, with the flow determining how those smaller widgets accumulate. The explicitly placed items claim their cells first, and the flow fills the rest, which is a powerful combination for layouts that are partly designed and partly dynamic. Understanding that the flow is the fallback, not the master, prevents the confusion of expecting it to move items that have been explicitly placed.

.column-fill {
  display: grid;
  grid-auto-flow: column;
}

Check: With grid-auto-flow: column, how are new items placed?

Section 18 — minmax()

minmax() sets a track's minimum and maximum size: minmax(200px, 1fr) means "at least 200 pixels, but grow to take a share of the free space". It is the key to grids that are both flexible and protected — columns that grow with the screen but never shrink below a usable width, rows that expand with content but never collapse below a readable height. Without minmax, a track's size is either fixed, which cannot adapt, or flexible, which cannot protect; minmax combines the two, giving each track a floor and a ceiling, and the browser resolves the actual size within that range according to the available space and the content. That combination is what makes responsive grids feel robust rather than fragile.

The minimum and maximum can each be any track size: a fixed length, a fraction, a percentage, or a content size such as auto, min-content, or max-content. The most common combinations are minmax(200px, 1fr) for columns that must stay usable but should share free space, minmax(150px, auto) for rows that must stay tall enough but should grow with content, and minmax(0, 1fr) for fractional tracks that must be allowed to shrink below their content size, as discussed in Section 6. Each combination answers a different question: the first protects usability, the second protects readability, the third permits full flexibility. Learning to read a minmax expression as "at least this, at most that" makes every template that uses it immediately understandable, and writing your own minmax expressions becomes a matter of stating the two bounds you care about.

Minmax interacts with the free-space calculation in a way that is worth understanding. When the container is wide, tracks grow toward their maxima; when it is narrow, they shrink toward their minima, and if the minima together exceed the container, the grid overflows rather than violating a minimum. That overflow is a signal, not a failure: it tells you the minima are too large for the container, and the answer is either smaller minima, fewer columns, or a different layout at that width — the same family of answers as for flex overflow. A grid whose minima sum to more than a phone's width will overflow on a phone, and the fix is usually the auto-fit pattern of the next section, which reduces the column count when the minima do not fit, rather than forcing them to fit by violating them.

A useful mental image for minmax is a spring with stops: the track can stretch and compress, but only between its two stops, and the stops are what keep the layout usable at the extremes. Without stops, a flexible track can collapse to nothing on a narrow screen or balloon absurdly on a wide one. With stops, it stays within the range the design intended, and the layout degrades gracefully — columns drop out via auto-fit, rows grow with content, and nothing becomes unusable. Minmax is therefore not an advanced extra but a basic protective tool, and it should appear in nearly every template you write, even a simple one, because the extremes of screen size are where unprotected layouts break, and minmax is the cheapest insurance against those breaks.

.gallery {
  display: grid;
  grid-template-columns: repeat(3, minmax(200px, 1fr));
}

Check: What does minmax(200px, 1fr) guarantee about a column?

Section 19 — auto-fit and auto-fill`repeat(auto-fit, minmax(220px, 1fr))` creates as many columns as fit in the container, each at least 220 pixels wide, sharing the space. The browser calculates how many 220-pixel columns fit in the available width, creates that many tracks, and then lets the 1fr maxima share the leftover space, so the columns fill the row exactly. The difference between the two keywords is what happens to empty columns — tracks created by the calculation but not occupied by any item. `auto-fill` keeps them as empty tracks, so the occupied columns keep their calculated width and the empty tracks sit as blank space at the end of the row. `auto-fit` collapses the empty tracks to zero, so the occupied columns expand to fill the row, sharing the space the empty tracks would have taken.

The practical consequence is that auto-fit is usually what you want for a gallery or card listing, because it makes the last row's cards stretch to fill the width, producing a full, balanced row even when the item count does not divide evenly by the column count. auto-fill is what you want when the columns must keep a consistent width regardless of item count — for example, a grid of placeholders where empty cells are part of the design, or a layout where items will be added dynamically and should not resize when a new item arrives. The two keywords answer different questions: auto-fit asks "how many columns do these items need, and how should they share the row?", while auto-fill asks "how many columns fit here, regardless of how many items there are?" Choosing between them is a choice about whether empty space should be absorbed by the items or left as empty tracks.

Both keywords require a minmax track, because the calculation needs a minimum size to determine how many tracks fit. repeat(auto-fit, 1fr) without a minimum is invalid for this purpose, since a fractional track has no minimum width to count against the container — the browser cannot determine how many of them fit, because any number of them would fit by shrinking. The minmax minimum is therefore not optional decoration but the input to the fit calculation, and changing it changes the column count: a minimum of 220 pixels in a 1000-pixel container fits four columns, while a minimum of 300 pixels fits three. That relationship — minimum size determines column count — is the mechanism behind the responsive behaviour of the next section, and it is worth experimenting with: change the minimum in a browser and watch the columns appear and disappear.

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}

The choice between the two keywords also matters for loading states. A grid that will receive more items later, for example from a search that loads in stages, may look jumpy if the existing items resize every time a new item arrives and the column count changes.

Check: How does auto-fit differ from auto-fill when there are empty columns?

Section 20 — A responsive grid without media queriesThe `auto-fit` plus `minmax` pattern is responsive by itself: on a wide screen many columns fit, on a narrow screen fewer fit, and the columns always share the space. No breakpoint is needed for the wrapping behaviour, because the column count is a continuous function of the container's width rather than a set of discrete steps chosen in advance. Resize the window slowly and the columns do not jump at breakpoints; instead, the existing columns grow wider as the window widens, until enough room appears for another column, at which point a new column appears and all columns narrow to accommodate it. That smooth, self-adjusting behaviour is the pattern's great virtue, and it is why it has become the standard recipe for galleries, card listings, and product grids.

The pattern's responsiveness has limits that are worth stating honestly. It changes the column count, but it does not change anything else: font sizes stay the same, gaps stay the same, the cards' internal layout stays the same, and the page's overall structure stays the same. A design that needs a fundamentally different arrangement on a phone — a sidebar moving below the content, a navigation bar collapsing, a dashboard rearranging its widgets — still needs media queries, because those are structural changes, not column-count changes. The auto-fit pattern handles the gallery's responsiveness; media queries handle the page's responsiveness; and the two work together, with the pattern reducing the number of media queries needed, not eliminating them. A page that uses auto-fit for its galleries and media queries for its skeleton is a page whose responsive behaviour is both smooth and deliberate.

There is also a minimum-width consideration. The pattern's smallest column is set by the minmax minimum, and on a very narrow screen — a phone in portrait, for example — even one column at the minimum may be wider than the container, causing overflow. The remedy is to choose a minimum that fits the narrowest screen you support, or to add a media query at small widths that reduces the minimum, or to let the single column shrink below its minimum by using a smaller minimum in the first place. A minimum of 220 pixels fits a 360-pixel phone screen with room for padding, but a minimum of 320 pixels does not, and that difference is the difference between a gallery that works on a phone and one that overflows. Choosing the minimum is therefore a decision about the narrowest supported width, not just about the desktop appearance, and it should be tested at that width explicitly.

.responsive-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}

The pattern also works well for user generated content, where the number of items and their sizes are unknown in advance. A community gallery, a list of user posts, or a set of uploaded photos will not have a tidy item count, and a layout that depends on a fixed column count would need constant adjustment.

Check: Why does this pattern need no media query to change its column count?

Section 21 — justify-items

justify-items aligns items within their cells along the row axis — horizontally, in the usual left-to-right flow. start puts each item at the left of its cell, center in the middle, and stretch, the default, makes it fill the cell's width. It sets the default for all items in the container, and it only has a visible effect when an item is narrower than its cell; a stretched item already fills its cell, so its justification is moot. That precondition — item smaller than cell — is the key to understanding all the within-cell alignment properties: they position items inside cells that are larger than the items, and when the item fills the cell, there is nothing to position.

The distinction between justifying items and justifying tracks is the most important conceptual point in this group of sections, and it is worth stating with care. justify-items moves items within their cells. justify-content, covered in Section 23, moves the tracks within the container. The two operate at different levels: items within cells, tracks within the container. Confusing them produces layouts where the wrong thing moves — tracks shifting when you meant items to shift, or items staying put when you meant the whole grid to move. The naming helps if you read it literally: "items" in the property name means the items are what moves, "content" means the grid's content — its tracks — is what moves. Keeping that reading in mind resolves the confusion in most cases.

In practice, justify-items is used less often than its flex counterpart, because grid items usually stretch to fill their cells by default, and that filling is usually what the design wants — cards filling their columns, panels filling their areas. The cases where it matters are those where an item should not fill its cell: a small badge in a large cell that should sit at the left, a button in a wide cell that should centre, an image in a tall cell that should sit at a particular horizontal position. In those cases, setting justify-items on the container gives every item that positioning, and justify-self on an individual item, covered in Section 25, overrides it for one item. The pair — container default plus item exception — mirrors the align-items and align-self pair from Flexbox, and the same guideline applies: set the container value for the common case, and use the item value sparingly for exceptions.

A final note is that justify-items has a legacy value, justify-items: stretch, which is the default, and a set of values shared with the other alignment properties: start, end, center, stretch, and the baseline values in some contexts. The start and end values follow the writing direction, so in a right-to-left page, start means the right side of the cell, just as flex-start means the right side of a flex row in such a page. That consistency across layout modes is deliberate: alignment values mean the same thing in Grid and Flexbox, so learning them once serves both. When you set justify-items, you are speaking the same alignment language as in Chapter 9, applied to a new level of the layout.

.grid {
  display: grid;
  justify-items: center;
}

Check: What does justify-items control — the grid tracks, or the items inside their cells?

Section 22 — align-items

align-items is the vertical counterpart of justify-items: it aligns items within their cells along the column axis. start puts them at the top of the cell, center in the middle, end at the bottom, and stretch, the default, fills the cell's height. Together with justify-items, it controls both directions of within-cell alignment, and the pair can be set together with the place-items shorthand of Section 24. As with justify-items, it only matters when an item is smaller than its cell on the relevant axis; a stretched item fills its cell and has nowhere to move, so its alignment value is irrelevant until something — a fixed height, a content size, an align-self override — makes it smaller than its cell.

The vertical direction is where within-cell alignment most often matters in practice, because grid rows are frequently taller than their items. A row of cards where one card contains a short notice and the others contain long articles will have a row height set by the longest article, and the short notice's card will stretch to fill it by default — usually fine. But a row containing a small icon, a heading, and a button, where the row height is set by the tallest item, may want the smaller items centred vertically rather than stretched, so they do not become tall, empty boxes. Setting align-items: center on the container centres every item in its cell vertically, producing a row where each item keeps its natural height and shares a vertical centre — the grid equivalent of the flex toolbar alignment from Chapter 9, applied cell by cell.

It is worth comparing the default stretch behaviour in Grid with the same default in Flexbox, because the two are similar but not identical in effect. In Flexbox, stretch applies along the cross axis only — a row's items stretch vertically, but their widths are governed by the main-axis sizing. In Grid, stretch applies on both axes by default: items stretch to fill their cells horizontally and vertically, because a cell is a two-dimensional rectangle and the item's default is to fill it completely. That means a grid item with no sizing declarations will exactly fill its cell, which is why grid layouts so often look "full" without any item sizing at all. If you want an item to keep its natural size in a cell, you must say so on one or both axes, with the alignment properties or with explicit sizes.

The interaction with spanning is also worth noting. An item spanning several rows occupies a tall rectangle, and its vertical alignment within that rectangle follows the same rules: stretch fills it, center centres within it, start puts it at its top. A sidebar spanning three rows, for example, will stretch to fill all three by default, giving the full-height sidebar effect, but if it has align-self: start, it will keep its natural height and sit at the top of the spanned area, leaving empty space below. Both are legitimate designs — a full-height sidebar versus a top-aligned one — and the alignment property is what chooses between them. The spanning and the alignment work together, with spanning defining the rectangle and alignment defining the item's position within it.

.grid {
  display: grid;
  align-items: center;
}

Check: If a grid item is shorter than its row, which property centres it vertically in its cell?

Section 23 — justify-content and align-content

When the whole grid is smaller than its container, justify-content spreads the tracks horizontally and align-content spreads them vertically — the same idea as in Flexbox, applied to tracks instead of items. With values like space-between or center, you can position a small grid inside a large container: centre it, push its tracks to the edges, or distribute them evenly. The precondition is the same as for the other alignment properties: there must be spare space. A grid whose tracks already fill its container has no space for content alignment to distribute, and the values will appear to do nothing. That precondition is the most common reason these properties seem broken — the grid is full, so there is nothing to move.

The values mirror the justify family from Chapter 9: start, end, center, stretch, space-between, space-around, and space-evenly, each applied to tracks rather than items. justify-content: center centres the columns as a group within the container's width; align-content: center centres the rows as a group within its height; together, they centre the whole grid in its container, which is the Grid equivalent of the flex dead-centre pattern, but at the track level rather than the item level. space-between on the tracks spreads the first column to the left edge and the last to the right, with even space between columns — a layout that suits a grid of panels inside a fixed-size dashboard area, where the panels should distribute across the available width rather than bunching at one side.

A concrete case where content alignment matters is a grid with fixed-size tracks inside a larger container. Suppose a gallery has columns of 200 pixels each, but the container is 1000 pixels wide: three columns take 600 pixels plus gaps, leaving spare space, and justify-content decides where that spare space goes — at the end with start, split evenly at both ends with center, or distributed between the columns with space-between. Without a content alignment declaration, the default start packs the tracks at the beginning, which is usually fine, but a design that wants the gallery centred or distributed needs the explicit value. The same applies vertically when the grid's rows total less than the container's height, which happens in fixed-height panels and dashboards more often than in scrolling pages, where the container height is usually determined by the content and there is no spare vertical space.

It is easy to confuse content alignment with item alignment, so a summary of the four properties helps. justify-items and align-items move items within cells. justify-content and align-content move tracks within the container. The first pair answers "where does this item sit in its cell?"; the second answers "where does the whole grid sit in its container?" Both pairs use the same values, and both need spare space to be visible, but they operate at different levels, and choosing the wrong level is the most common Grid alignment error. When a grid's alignment looks wrong, ask which level you meant — item or track — and check that you set the property for that level, not the other.

.grid {
  display: grid;
  justify-content: center;
  align-content: center;
}

Check: When do justify-content and align-content have a visible effect on a grid?

Section 24 — place-items shorthand

place-items sets align-items and justify-items in one declaration, in that order: place-items: center centres items in their cells both ways, while place-items: start center aligns them to the top of their cells and centres them horizontally. There is also place-content for the content properties — align-content first, then justify-content — and place-self for a single item's align-self and justify-self, following the same pattern. The order is the trap: place-items takes align first, then justify, which is the reverse of the order in which most developers think about horizontal and vertical, and writing place-items: center start when you meant the opposite will produce a layout that is correct in one direction and wrong in the other, with no error to tell you why.

The shorthand's value is brevity in the common case. place-items: center is the standard way to centre items within their cells in both directions, and it appears constantly in grid code for icons in tiles, numbers in statistic panels, and content in cards that should sit in the middle of their cells. Writing it as one declaration rather than two keeps the stylesheet shorter and makes the intention — "centred both ways" — immediately readable. The two-value form is used less often, because most within-cell alignment is either centred both ways or stretched both ways, but it is there for the cases where the two directions genuinely differ, such as items that should sit at the top of their cells but centred horizontally.

It is worth learning the whole place family together, because the pattern recurs. place-items is align-items plus justify-items. place-content is align-content plus justify-content. place-self is align-self plus justify-self. In each case, the first value is the block-axis property (align, vertical in the usual flow) and the second is the inline-axis property (justify, horizontal in the usual flow), and a single value sets both. That consistency means learning one shorthand teaches you the other two, and it means you can predict the behaviour of a place declaration you have not seen before by remembering the order: align, then justify. The family is a small example of CSS's design philosophy — related properties grouped under a shared pattern — and recognising the pattern makes the whole alignment system feel more coherent.

A caution about shorthands in general applies here too: a shorthand resets the properties it covers, including ones you may have set elsewhere. Writing place-items: center after setting align-items: start in an earlier rule will override the align value to center, because the shorthand sets both properties, even the one you did not intend to change. That reset behaviour is standard for CSS shorthands, but it surprises developers who think of place-items: center as "also centre this" rather than "set both alignments to center". When overriding alignment in a later rule, prefer the longhand property for the axis you mean to change, and reserve the shorthand for the initial declaration where both axes are being set together. That discipline prevents the quiet override that is hard to spot in a long stylesheet.

.grid {
  display: grid;
  place-items: center;
}

Check: What does place-items: center set?

Section 25 — Aligning a single item

justify-self and align-self override the container's alignment for one item only — the same idea as Flexbox align-self, extended to both axes. If every card is stretched but one badge should sit at the top-left of its cell, give that item align-self: start and justify-self: start. If every item is centred but one panel should fill its cell, give it align-self: stretch and justify-self: stretch. The item properties take the same values as the container properties, and they win over the container's defaults for that item alone, leaving its siblings where they were. That exception mechanism is what makes grid alignment practical: the container sets the rule, and individual items opt out where the design requires it.

The typical uses are small and specific, which is as it should be — a grid where every item has its own alignment is a grid whose container alignment was probably wrong. A featured card that should stretch while its neighbours centre; a logo that should sit at the top of its area while the rest of the header centres; a button in a wide cell that should hug the right edge while other items hug the left. In each case, the exception is one item, and the declaration is one or two properties on that item, which keeps the stylesheet readable: the container rule is visible as the norm, and the item rule is visible as the deliberate exception, often with a comment explaining why it differs. That readability matters, because a future developer who sees an item with a different alignment should be able to tell whether it is intentional or accidental, and a single, clear declaration with a reason is the best evidence of intention.

Self-alignment interacts with explicit sizing in a way worth knowing. An item with a fixed width narrower than its cell and justify-self: stretch will not stretch, because the fixed width wins over the stretch instruction — stretch only fills the cell when the item has no definite size on that axis. Similarly, an item with a fixed height and align-self: stretch keeps its height. The rule is consistent with Flexbox: stretch is a default for items without a size, not an override of a size you deliberately set. If a self-aligned item refuses to stretch, check for a fixed size on that axis before assuming the alignment is broken, just as you would in Flexbox. Alignment and sizing are separate concerns, but they meet at the point where a definite size limits what alignment can do.

There is also the place-self shorthand, which sets align-self and justify-self together, align first, following the same order as place-items. place-self: center centres one item in its cell both ways, which is the grid equivalent of the flex dead-centre pattern applied to a single item — useful for a modal-like panel in a grid area, or an icon centred in a tile. As with the other shorthands, it resets both properties, so use it for the initial declaration and the longhands for later overrides. The self-alignment family completes the alignment system: container defaults, item exceptions, and shorthands for both, in a consistent pattern that, once learned, covers every within-cell alignment need you will encounter in Grid.

.badge {
  justify-self: start;
  align-self: start;
}

Check: How do you align just one grid item differently from the rest?

Section 26 — Grid versus Flexbox: choosing the right toolThe choice between Grid and Flexbox is the most important layout decision after the choice of structure itself, and the rule is simple to state: one dimension, Flexbox; two dimensions, Grid. A navigation bar, a toolbar, a row of buttons, a form row — all one-dimensional, all Flexbox. A page skeleton, a dashboard, a gallery where rows must align, a calendar — all two-dimensional, all Grid. The rule works because it matches each tool's model: Flexbox arranges a line, Grid defines a structure, and the layout's dimensionality tells you which model it needs. When the rule is applied consistently, layouts become simpler, because each part uses the tool designed for its shape, rather than one tool stretched to cover every shape.

The interesting cases are the ones where both tools could work, and those are worth examining, because they teach the boundary. A row of equal-height cards can be built in either: Flexbox gives you wrapping and equal heights within each row, while Grid gives you aligned columns across rows and equal row heights across the whole grid. Which is better depends on what matters: if the cards should reflow freely and each row is independent, Flexbox is simpler; if the cards should form a strict grid where columns line up from row to row, Grid is more precise. A set of tabs can be built in either, but Flexbox is natural because tabs are a line, while Grid would add structure the tabs do not need. A dashboard can be built in either, but Grid is natural because widgets occupy rectangles in a two-dimensional arrangement, while Flexbox would require nested rows and columns to imitate the same rectangles. In each borderline case, ask which tool's model matches the design's intent, not which tool you happen to know better.

Combining the tools is not just allowed but expected, and the combination is where real layouts live. A page built with Grid for its skeleton will have Flexbox inside its regions: the header region is a flex row for its logo and navigation, the sidebar region is a flex column for its links, the card region is a flex row or a nested grid for its cards. Conversely, a flex row of cards will have Grid inside a card if the card's interior needs two-dimensional arrangement, such as an image beside text with a footer spanning both. The nesting principle from Chapter 9 applies here too, extended across tools: each level uses the tool that fits its own arrangement, and the levels do not interfere, because each container manages only its direct children. A developer who nests Grid and Flexbox freely, choosing per level, will build layouts that are both simpler and more robust than one who insists on a single tool for the whole page.

/* One direction: Flexbox. Two directions: Grid. */

Check: What question should you ask to decide between Grid and Flexbox for a layout?

Section 27 — A full page layout with areasA full page layout is the showcase application of grid-template-areas: a header across the top, a sidebar on the left, main content in the middle, and a footer across the bottom, drawn as text and claimed by name. The template states the page's structure in a form that anyone can read, the items claim their areas, and the browser assembles the page. The column template gives the sidebar its fixed width and the main area its flexibility; the row template, or the content's natural sizing, gives the header and footer their heights; and the gap gives the regions their breathing room. That combination — areas for placement, columns for widths, gaps for gutters — is the complete page skeleton in a handful of declarations, and it replaces the nested wrappers and floats that used to be required for the same result.

Building the page in stages makes the process clear. First, write the HTML with semantic elements: a header, a nav or aside for the sidebar, a main for the content, and a footer. Second, make the wrapper a grid container and write the areas drawing, with one string per row and one word per cell. Third, write the column template, typically a fixed sidebar and a flexible main area. Fourth, give each element its grid-area name. At that point the page assembles itself, and you can verify it by resizing the window: the sidebar stays fixed, the main area flexes, the header and footer span the full width, and the gaps stay uniform. If any region is misplaced, the drawing is the first place to check, because the drawing is the single source of truth for the page's geometry.

There are details that make the page layout robust. The sidebar's height is handled by spanning: if the sidebar area covers the rows of the main content, it stretches to match them, giving a full-height sidebar without a height declaration. The footer's position is handled by the drawing: it sits in the last row, below everything, regardless of content height, though on a very short page it will sit directly below the content rather than at the bottom of the viewport — for a viewport-bottom footer, combine the grid with the flex sticky-footer pattern from Chapter 9, or give the wrapper a minimum height and let the content row grow. And the main area's interior is handled by nesting: the main element can itself be a grid or flex container for its cards, articles, or widgets, following the nesting principle. The page skeleton is one level; the regions' interiors are the next levels; and each level uses the tool that fits it.

.page {
  display: grid;
  grid-template-columns: 220px 1fr;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
}

Check: When the page stacks into one column on a phone, what needs to change — the items' grid-area values, or the template drawing?

Section 28 — Header and footer spanningHeaders and footers in a grid page almost always span the full width, and Grid offers three ways to express that spanning, each suited to a different situation. The areas way is to repeat the name across all columns in the drawing, so the header area covers the whole top row. The line way is `grid-column: 1 / -1`, spanning from the first line to the last, regardless of column count. The span way is `grid-column: span all`, though that form is less widely used and the line way is the standard. All three produce the same visual result — a header stretching across the page — but they differ in maintainability: the areas way keeps the spanning in the drawing, the line way keeps it on the item, and the choice depends on whether the page uses areas or lines for its overall placement.

The spanning header also raises a question about its internal layout. A header that spans the full width is itself a container for its contents — a logo, navigation, actions — and that interior is usually a flex row, following the combination principle from Section 26. The header is a grid item at the page level and a flex container at its own level, and the two roles coexist without conflict. Similarly, a footer spanning the full width often contains several groups of links, arranged as a flex row or a nested grid within the footer. That nesting — grid for the page, flex for the header's interior — is the normal structure of a real page, and it is worth building deliberately rather than letting the header's contents fall into a block stack by accident. A spanning region is not just a wide box; it is a region with its own internal arrangement, and that arrangement deserves its own container declaration.

Spanning also interacts with gaps in a way that is easy to miss. A header spanning three columns spans the columns and the gaps between them, so its width includes two gaps. That is correct — the gaps are part of the spanned area — but it means the header's content, if it has its own padding, sits inside a box that already includes the grid's gaps at its edges, which can make the header's content appear inset compared with a non-spanning region's content. The usual remedy is to give the header its own internal padding that accounts for the visual rhythm, or to accept the inset as part of the design. The point is not to calculate the exact pixels but to notice the relationship: spanning crosses gaps, and the visual result includes those gaps, so a spanned region's interior spacing should be judged by eye, not assumed from the template alone.

.page-header {
  grid-column: 1 / -1;
}

Check: Name two ways to make a header span the full width of a grid.

Section 29 — Sidebar layout in GridA sidebar layout in Grid is a two-column grid: a fixed column for the sidebar and a flexible column for the main content, with the sidebar optionally spanning several rows so it stretches the full height of the content. Compared with the Flexbox sidebar from Chapter 9, the Grid version has one decisive advantage: the sidebar's height and the main area's rows are part of the same structure, so a sidebar spanning the content rows automatically matches their combined height, and widgets in the main area can be placed in a sub-grid whose columns align with the page's columns if desired. The Flexbox version handles the widths just as well, but the Grid version handles the two-dimensional relationship — sidebar height against content rows — with less effort, because that relationship is structural in Grid and incidental in Flexbox.

The column template is the main decision. A fixed sidebar of 220 or 260 pixels suits navigation with text labels; a narrower sidebar of 60 pixels suits an icon rail; a sidebar that should grow on very wide screens might use a minmax range instead of a fixed size. The main area takes the rest, usually as 1fr, and the gap separates the two regions clearly. If the page also has a header and footer, the sidebar area spans the middle rows in the drawing, while the header and footer span all columns, producing the classic page skeleton from Section 27. The sidebar layout is thus not a separate pattern from the page layout but a part of it — the middle rows of the page, with the sidebar in one column and the content in the other — and building it as part of the page grid, rather than as a separate nested layout, keeps the structure in one place.

Responsive behaviour for a Grid sidebar follows the same pattern as for the page: at a breakpoint, the drawing is rewritten to stack the sidebar and content, and the column template becomes a single column. The question of where the sidebar goes when stacked — above the content, where it is immediately visible, or below it, where the main content comes first — is a content decision, not a technical one. Navigation that users need to reach the content belongs above or in a toggle; secondary navigation, such as related links, can go below without harming the experience. The areas drawing makes either choice a one-line change, because the sidebar's area name simply moves to a different row in the drawing, and its item declaration stays untouched. That ease of rearrangement is why areas are the recommended tool for page skeletons: the responsive design becomes a set of drawings, one per breakpoint, each readable as a sketch.

.layout {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 24px;
}

Check: What advantage does the Grid sidebar have over the Flexbox sidebar for matching the sidebar's height to the content?

Section 30 — A card gallery

A card gallery in Grid is a container with a column template — usually the auto-fit pattern from Section 20 — and cards as items, each filling its cell. Compared with the Flexbox card row from Chapter 9, the Grid gallery has aligned columns across rows: every card in a column shares that column's width, because the width belongs to the track, not to the card, and every row's height is determined by the grid's row sizing, so cards in a row share a row height by structure rather than by stretching. That structural alignment is the Grid gallery's advantage, and it produces the tidy, magazine-like regularity that users associate with a professional gallery, even when the cards contain different amounts of text.

The card's interior is, as usual, a separate level. Each card is a grid item at the gallery level and a flex column at its own level, with its title, text, and button stacked, and its button pushed to the bottom with an auto margin, as in Chapter 9's card pattern. That combination — Grid for the gallery, Flexbox for the card — is the standard architecture for card layouts, and it is worth building explicitly rather than trying to make the gallery's grid handle the cards' interiors as well. The gallery decides where cards go and how big their cells are; the card decides how its own contents are arranged within its cell; and neither level needs to know about the other's decisions, which keeps both levels simple and independently changeable.

Row heights in a gallery deserve attention, because they determine whether the gallery looks regular or ragged. By default, each row sizes to its tallest card, so rows with different content heights will have different heights — a natural result, but one that can look uneven if the variation is large. Setting grid-auto-rows to a fixed height or a minmax range imposes regularity: every row the same height, or at least the same minimum, with taller content either growing its row or being handled inside the card. The choice depends on the design: a photo gallery with uniform images wants fixed rows for a strict grid; an article gallery with varied text wants content-sized rows for a natural feel, accepting some variation. Either way, the row sizing is a deliberate decision, made with auto-rows, not an accident of content.

Images in gallery cards follow the same guidance as in Flexbox: constrain them with max-width, give them a consistent aspect ratio or object-fit treatment, and reserve their space to prevent layout shift. In a Grid gallery, there is an additional consideration: an image that is wider than its cell will force its column wider if the column's minimum allows it, or overflow if it does not, so the image constraint is also a column constraint. Setting minmax(0, 1fr) on the columns, or constraining the images, prevents a single wide image from distorting the whole gallery's column structure. That interplay — content size versus track size — recurs throughout Grid, and the gallery is where it is most visible, because a gallery's whole point is regularity, and a single unconstrained image can break that regularity for every card in its column.

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}

Check: What does Grid give a card gallery that a Flexbox card row does not?

Section 31 — Overlapping itemsGrid allows items to overlap: place two items into the same cells, and they will occupy the same rectangle, stacking on top of each other. Unlike Flexbox, which arranges items without overlap by design, Grid places items where told, and if two items are told to go to the same place, they both go there. The stacking order follows the document order by default — later items paint above earlier ones — and can be controlled with z-index, which Chapter 11 covers in depth. Overlap in Grid is therefore a deliberate technique, not an accident, and it enables designs that would otherwise require absolute positioning: a caption overlaid on an image, a badge over a card, a heading overlapping two regions of a page.

The classic overlap pattern is an image with a caption or a call-to-action overlaid on it. Place both the image and the caption in the same grid area or the same cells, align the caption within the cell — for example, to the bottom with align-self: end — and the caption sits on top of the image, positioned by the grid rather than by absolute coordinates. The advantage over absolute positioning is that the overlay remains part of the grid's flow: if the image's cell grows, the overlay's position follows, because both are positioned relative to the same cell, not to coordinates that must be recalculated. The overlay also remains in the document flow for accessibility purposes, in a way that an absolutely positioned element, removed from flow, does not always achieve as cleanly, though both techniques require care about reading order and focus order.

There are cautions. Overlapping content can become unreadable if the top item obscures essential parts of the one beneath — text over a busy photograph, for example, needs contrast treatment, such as a gradient or a solid background behind the text, to remain legible. Overlapping interactive elements can create hit-testing problems, where the top element intercepts clicks meant for the one beneath, which is solved by pointer-events or by ordering the elements correctly, but must be considered deliberately. And overlapping items still occupy their cells for layout purposes, so a tall overlapping item can affect its row's height even though it visually shares space with another item. Overlap is a powerful Grid feature, but like all powerful features, it should be used where the design genuinely calls for layering, not as a casual way to nudge elements, which is what positioning and margins are for.

/* Both items claim the same area: they overlap. */
.hero-image {
  grid-area: hero;
}

.hero-caption {
  grid-area: hero;
  align-self: end;
}

Overlap can also be used for decorative layering that is not tied to a single image. A heading that spans two columns while a background panel spans the same columns behind it creates a layered title block, with the panel providing colour and the heading providing text, both positioned by the same grid. Because both items share cells, they move together when the grid changes, which keeps the layering intact across screen sizes.

Check: How can two grid items end up occupying the same cells?

Section 32 — grid-area shorthand`grid-area` as four values — row start, column start, row end, column end, in that order — is the line-based placement shorthand, and it is the most compact way to place an item in a precise rectangle. `grid-area: 1 / 2 / 3 / 4` puts the item in rows 1 to 3 and columns 2 to 4, covering two rows and two columns. The order is the trap: row first, then column, then row end, then column end, which is the reverse of the order in which most developers think about horizontal before vertical, and it is easy to write the values in column-row order by mistake, producing a rectangle that is transposed from the intended one. Reading the shorthand as "row start, column start, row end, column end" — and remembering that it mirrors the order of the template properties, rows before columns in the shorthand's own logic — helps fix the order in memory.

The shorthand's values follow the same family as the grid-column and grid-row shorthands: line numbers, negative numbers, named lines, and spans, on each of the four sides. So grid-area: 1 / 1 / span 2 / span 3 starts at the first row and column and spans two rows and three columns, while grid-area: header — a single identifier — is the area-name form from Section 11, not a shorthand at all. The browser distinguishes the forms by their shape, but a human reader must distinguish them by counting values: one value is a name, four values are lines, and anything else is invalid. That distinction is worth practising, because stylesheets mix the two forms freely, and misreading a four-value placement as a name, or vice versa, leads to confusion about where an item is supposed to go.

When should you use the four-value shorthand instead of the separate grid-column and grid-row declarations? When the placement is a single, atomic rectangle that should be read as one unit — a dashboard widget's position, a hero's span, a sidebar's extent — the shorthand expresses it as one declaration, which is more readable than two declarations that must be combined mentally to understand the rectangle. When only one axis needs setting, or when one axis is set in one rule and the other in a media query, the separate properties are clearer, because they allow independent changes without restating the whole rectangle. The choice is about readability and maintainability, not about capability: both forms place the item identically, and a stylesheet that mixes them thoughtfully — shorthand for complete rectangles, longhands for single-axis changes — is easier to follow than one that uses either form exclusively.

.widget {
  grid-area: 1 / 2 / 3 / 4;
}

A frequent error with the four value form is to confuse it with the area name form when reading code written by someone else. A single word that looks like a number, or a set of numbers separated by slashes, can be misread at a glance, especially in a long stylesheet. Naming the area or adding a brief comment beside an unusual placement helps the next reader, and helps your future self, to see the intention without counting slashes.

Check: In grid-area: 1 / 2 / 3 / 4, which numbers are rows and which are columns?

Section 33 — Naming grid lines

Grid lines can be given names, placed in square brackets in the template: grid-template-columns: [sidebar-start] 220px [sidebar-end main-start] 1fr [main-end] names the lines at the edges of the sidebar and the main area, with the middle line carrying two names, one for each side it bounds. Items can then be placed by those names instead of numbers: grid-column: sidebar-start / main-end spans the full width, and grid-column: main-start / main-end places an item in the main column. Named lines make placement self-documenting — the declaration says what it means, not just which numbers it uses — and they survive template changes better than numbers, because a line's name stays with it even when columns are added before it and its number changes.

Naming lines is most valuable in grids whose structure changes across breakpoints or whose column counts vary. A dashboard with a sidebar, a main area, and a details panel might name its lines rail-start, rail-end, content-start, content-end, details-start, details-end, and items placed by those names will follow their regions even if a breakpoint removes the details panel and renumbers the lines. Number-based placement in the same situation would need updating at every breakpoint, because the numbers shift when a column is removed, while name-based placement needs no change at all, as long as the names still exist in the new template. That robustness is the same benefit as area names, expressed at the line level, and it makes named lines the preferred placement method for complex, responsive grids where numbers would be brittle.

There are conventions for naming that make the names more useful. Name lines after the regions they bound, not after their positions: sidebar-start and sidebar-end are better than line-1 and line-2, because the region names carry meaning while the position names merely restate the numbers. Give a shared line both names — the end of one region and the start of the next — so that placement can be expressed from either side's perspective, as in the example above, where the middle line is both sidebar-end and main-start. And use consistent naming across breakpoints, so that the same names exist in every template variant, even if their positions differ, which is what allows item placements to survive breakpoint changes untouched. Those conventions turn named lines from a curiosity into a maintainability tool.

Named lines also combine with the repeat function in a useful way: repeat(3, [col-start] 1fr [col-end]) names the lines around each repeated column, though the repeated names are then ambiguous — there are three lines named col-start — and placement by such a name resolves to the first matching line, which limits its usefulness for individual columns. For repeated tracks, area names or numeric placement are usually clearer than repeated line names, and named lines are at their best for the distinct, structural lines of a page or dashboard: the edges of the sidebar, the main area, the header, and the footer. Knowing where a technique shines, and where it does not, is part of using it well, and named lines shine at the structural edges, not inside repeated patterns.

.layout {
  display: grid;
  grid-template-columns: [sidebar-start] 220px [sidebar-end main-start] 1fr [main-end];
}

Check: Why are named lines more robust than line numbers when a grid's structure changes?

Section 34 — Nested gridsA grid item can itself be a grid container, and this nesting is how complex layouts are built: a page grid holds regions, each region holds a grid or flex container for its own contents, and each of those holds items that may themselves be containers. Each level defines its own structure — its own columns, rows, gaps, and areas — and the levels do not interfere, because each container manages only its direct children. A dashboard page might be a grid of regions, with the main region containing a grid of widgets, and a widget containing a grid of statistics, each level's template describing only that level's arrangement. That decomposition is what makes large layouts manageable: no single template has to describe the whole page, and each template stays small enough to read at a glance.

The relationship between a nested grid and its parent is worth understanding precisely. The nested container is an item in the parent grid, so its size is determined by the parent — it stretches to fill its cell, or sizes according to its alignment — but its internal structure is entirely its own: its columns do not align with the parent's columns, its rows do not align with the parent's rows, and its gaps are independent. That independence is both a strength and a limitation. It is a strength because it lets each region have the structure its content needs, without forcing the whole page into one uniform grid. It is a limitation because it prevents the kind of cross-region alignment that a single flat grid provides — for example, a widget's columns lining up with the page's columns across a region boundary. For that cross-boundary alignment, subgrid, covered next, is the tool, and it exists precisely because ordinary nesting cannot provide it.

There is a practical point about gaps and nesting that is easy to overlook. Each nested grid has its own gap, and the gaps accumulate visually: a page gap of 24 pixels plus a region gap of 16 pixels produces a different rhythm at the region boundary than within a region, which may or may not be the design's intent. Consistent gap values across levels produce a harmonious rhythm, while deliberately different gaps can separate regions visually — a larger page gap to distinguish regions, a smaller region gap for closely related items. Either choice is valid, but it should be deliberate, and it should be consistent across the page, so that the spacing feels designed rather than accidental. Gaps, like templates, are part of each level's design, and they deserve the same deliberate attention at every level.

.region {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

Nesting also affects how a layout responds to changes in content. When a region grows because its own content grows, its parent grid may grow too, because the parent row or column sizes to the region. That growth then affects neighbouring regions, even though their own content has not changed.

Check: Does a nested grid's columns automatically align with its parent grid's columns?

Section 35 — SubgridSubgrid is a value for grid-template-columns and grid-template-rows that lets a nested grid adopt its parent's tracks instead of defining its own. A grid item with `grid-template-columns: subgrid` does not create new columns; it uses the parent grid's columns, so its own children align with the parent's columns across the region boundary. That solves the limitation from Section 34: where ordinary nesting breaks alignment at the boundary, subgrid preserves it, allowing a card's internal columns to line up with the page's columns, or a list item's fields to line up across rows, even though the items are nested inside a parent item. It is the feature that makes deeply structured layouts align as if they were flat, while keeping the semantic nesting that a flat grid would lose.

The classic use case is a card or list where each item contains several fields that should align across items. Imagine a list of products, each a grid item containing a name, a price, and a rating, and the design requires all names to share a column width, all prices to share another, across the whole list. With ordinary nesting, each product's internal grid sizes its own columns independently, so the names end up different widths. With subgrid, each product adopts the parent's columns, so the names, prices, and ratings align across all products, as if the fields were direct children of the parent grid, while the HTML keeps the sensible nesting of fields inside products. That combination — semantic nesting plus flat alignment — is what subgrid was designed for, and it is a genuinely new capability, not just a convenience.

There are details of subgrid worth knowing before using it. A subgrid can adopt columns, rows, or both, independently — grid-template-columns: subgrid with an ordinary row template, for example, aligns columns but not rows. The subgrid item must span the parent tracks it wants to adopt, because it can only adopt the tracks it covers; a subgrid spanning two parent columns adopts those two, not the whole parent grid. And gaps: a subgrid's gaps are its own, but they interact with the parent's tracks in ways that require care, since the subgrid's items are placed on the parent's tracks but spaced by the subgrid's gaps. Those details make subgrid a feature to learn by experiment, building a small aligned list and watching how the tracks behave, rather than by reading alone. It is powerful, but its power comes with a learning curve that is best climbed with a concrete example in front of you.

.card {
  display: grid;
  grid-template-columns: subgrid;
}

One detail worth noting is that a subgrid's own gap still applies between its items, even though its tracks come from the parent. The spacing inside the subgrid and the spacing of the parent grid should therefore be chosen together, or the aligned columns will look uneven despite sharing tracks.

Check: What problem does subgrid solve that ordinary nested grids cannot?

Section 36 — min-content and max-content`min-content` and `max-content` are content-based track sizes that let a track size itself to its content in two different ways. `max-content` sizes the track to the content's preferred width — the width the content would take if it never wrapped, so a paragraph in a max-content column would be as wide as its longest line. `min-content` sizes the track to the content's minimum width — the narrowest the content can be without overflowing, so a paragraph in a min-content column would be as narrow as its longest word. The two are the extremes of content sizing: max-content is "as wide as the content wants", min-content is "as narrow as the content allows", and most real track sizes fall somewhere between them, governed by fixed sizes, fractions, or minmax ranges that use these keywords as bounds.

There is a caution about max-content that is worth stating plainly: a max-content track can become extremely wide, because content's preferred width is unbounded — a paragraph with no wrapping opportunities, a long URL, or a wide image can produce a max-content width of thousands of pixels, overflowing the container and breaking the layout. Max-content should therefore be used where the content is known to be short and bounded — navigation labels, short headings, small badges — not where the content is arbitrary user text or long prose. Min-content has the opposite risk: it can become extremely narrow, producing a tall, thin column of wrapped text that is hard to read, if the content's longest word is short but the text is long. Both keywords are precise tools for content-driven sizing, and both require judgement about the content they will hold, because content-driven sizing means the content, not the designer, sets the size.

In combination with the other sizing tools, content keywords complete the track-sizing vocabulary. Fixed sizes say "this big, always". Fractions say "a share of the free space". Content sizes say "as big as the content needs, in one of two senses". Minmax says "between these two bounds". And auto says "look at the content and the available space and decide" — a hybrid that behaves like max-content in some contexts and like a flexible size in others, which is why it is the default for many implicit tracks. Learning to choose among these is learning to state, for each track, what should determine its size: the design, the space, or the content. Most tracks are determined by the design or the space, and content sizing is reserved for the tracks where the content genuinely should decide — the sidebar that fits its labels, the label column that fits its longest word — which is why these keywords appear less often than fractions but matter greatly when they do.

.layout {
  display: grid;
  grid-template-columns: max-content 1fr;
}

Check: What is the difference between a min-content track and a max-content track?

Section 37 — auto track sizing`auto` as a track size means "size this track based on its content and the available space", and its exact behaviour depends on context, which makes it the most flexible and the most confusing track size. In a grid with free space, an auto track sizes to its content, like max-content, but it can also shrink or grow to help fill the container, unlike a pure content size. In a grid without free space, it behaves more like a content size, taking what its items need. That adaptability is why auto is the default for implicit tracks and for tracks you do not specify: it produces a sensible result in most situations, without requiring you to choose a specific sizing rule. But adaptability comes at the cost of predictability, and a track sized auto can surprise you when its content changes, because the track changes with it.

The most common place auto appears is as the maximum in a minmax range for rows: grid-auto-rows: minmax(150px, auto) means rows are at least 150 pixels tall but grow to fit their content, which is the standard pattern for a gallery whose cards have varied text lengths. It also appears as a column size in templates where one column should fit its content and another should take the rest: auto 1fr gives the first column its content width and the second everything else, which is the grid equivalent of the flex pattern with a natural-size label and a growing input. In both cases, auto is doing the job of "fit the content", and the other track is doing the job of "take the rest", and the pair together is a clean, readable statement of the layout's intent.

Auto's behaviour with fractions deserves a specific note, because it affects how free space is shared. When a grid contains both auto tracks and fractional tracks, the auto tracks are sized first, based on their content, and the fractional tracks share whatever remains. That means an auto column with long content can consume most of the container, leaving little for the fractional columns, which then shrink toward their minima or overflow. If a fractional column is being squeezed by an auto neighbour, the cause is usually the auto track's content growing unexpectedly — a long heading, a wide image — and the fix is to constrain that content, give the auto track a minmax bound, or replace it with a fixed or fractional size. Auto tracks are content-driven, and content-driven tracks need content that is itself constrained, or they will dominate the layout.

.layout {
  display: grid;
  grid-template-columns: auto 1fr;
}

Auto sizing also plays a role in form layouts built with Grid. A label column sized auto will take the width of the longest label, which keeps all inputs aligned without a fixed label width. That works well when labels are short and similar in length. When one label is much longer than the others, however, the auto column grows to fit it, and all inputs shift right, which can waste space and look unbalanced.

Check: In a grid with an auto column and a 1fr column, which is sized first?

Section 38 — Dense packingDense packing, set by `grid-auto-flow: row dense` or `column dense`, fills holes left by spanning items by pulling later, smaller items forward into earlier empty cells. In the default sparse packing, an item that does not fit in the remaining cells of a row moves to the next row, leaving a hole, and later items follow it in order, preserving the document sequence at the cost of empty space. In dense packing, the browser looks ahead: if a later item is small enough to fit the hole, it is placed there, even though it comes later in the document, and the grid becomes denser, with fewer empty cells. The trade-off is explicit: density versus order, and dense packing chooses density.

The visual benefit is clearest in a gallery with mixed spans, where featured items span two columns and ordinary items span one. With sparse packing, a featured item that does not fit at the end of a row leaves a single-cell hole, and the next featured item moves to a new row, producing a ragged gallery with visible gaps. With dense packing, the next ordinary item fills the hole, and the gallery looks full and regular, at the cost that the items' visual order no longer matches their document order — an item that is fifth in the HTML might appear third visually, because it was pulled forward to fill a hole. For a photo gallery, where the order is usually not critical, that trade is often worth it. For a list where order carries meaning — chronological posts, ranked results, steps in a process — it is not, and sparse packing's preservation of order matters more than its holes.

Dense packing also has accessibility implications that follow directly from the order change. Screen readers and keyboard users experience the document order, not the visual order, so a densely packed gallery presents two different sequences: the visual sequence, with items pulled forward, and the reading sequence, with items in document order. For a gallery of images, that mismatch is usually harmless, because the images are independent and their order is not load-bearing. For content where order matters — a tutorial's steps, a story's panels, a form's fields — the mismatch can confuse, because the visual sequence tells one story and the reading sequence tells another. The guideline from Chapter 9 applies here too: if order carries meaning, preserve it; if it does not, density may be worth the trade. Dense packing should be a deliberate choice for order-independent content, not a default applied to every grid.

.gallery {
  display: grid;
  grid-auto-flow: row dense;
}

Dense packing is also worth testing with keyboard navigation before adopting it. A user tabbing through a densely packed gallery will move in document order, which may jump visually from one side of the grid to another as the focus moves to an item that was pulled forward. That jumping can be disorienting if the focus indicator moves in a pattern that does not match the visual layout.

Check: What does dense packing trade away in exchange for filling holes in a grid?

Section 39 — Grid and imagesImages in Grid follow the same principles as in Flexbox, with one additional consideration: an image's size can affect its track, not just its cell. A wide image in a fractional column can force that column wider than its share, because the track's content minimum is set by the image's natural width, unless the column's minimum is zero or the image is constrained. The standard constraint, `max-width: 100%`, prevents the image from exceeding its cell, and it should be set for images in grid layouts just as in flex layouts — many projects set it globally, so no image can ever overflow its parent. With that constraint, the image scales down to fit its cell, and the track sizing proceeds as designed, rather than being distorted by a single large image.

Aspect ratio and object-fit are the other two image tools in Grid, and they matter more here than in Flexbox, because Grid cells are often uniform — a gallery's cells share a size, and images of different shapes must fit those cells consistently. Setting an aspect ratio on the image's container, or on the image itself, reserves a frame of a chosen shape, and object-fit: cover fills that frame by cropping, while object-fit: contain fits the whole image inside it with letterboxing. For a gallery, cover with a consistent aspect ratio produces the uniform, professional look that users expect: every cell shows an image of the same shape, regardless of the original photograph's proportions. For product images, where seeing the whole item matters, contain with a consistent frame preserves the item while still aligning the cells. Either way, the frame's shape is a design decision made in CSS, and the image adapts to it.

There is also the question of images as grid items themselves, rather than images inside grid items. An image placed directly as a grid item will stretch to fill its cell by default, which can distort it if the cell's aspect ratio differs from the image's. The fix is the same as for any grid item that should not stretch: set its alignment to something other than stretch, or set its size explicitly, so it keeps its natural proportions within the cell. A gallery of bare images — images as items, without card wrappers — therefore needs either uniform aspect ratios on the images, or alignment that preserves their proportions, or object-fit to fill the cells without distortion. The choice depends on whether the images are the content, in which case their proportions should be respected, or decoration within a uniform grid, in which case they should fill their cells.

.gallery img {
  max-width: 100%;
  object-fit: cover;
}

Grid images also raise a question about loading and layout shift. An image that has not yet loaded has no height, so its cell may start small and then grow when the image arrives, pushing other content down the page. Reserving space with an aspect ratio on the image container prevents that shift, because the frame exists before the image loads and the image simply fills it when it arrives.

Check: Why can a single wide image distort a whole grid column?

Section 40 — The holy grail layoutThe holy grail layout — a header across the top, a footer across the bottom, and three columns in between: navigation on the left, main content in the middle, and an aside on the right — was once the most difficult layout on the web, requiring floats, negative margins, and source-order tricks to achieve its signature features: equal-height columns, a flexible centre, and fixed sidebars. With Grid, it is a straightforward template: three columns, three rows, and areas for the five regions. The name "holy grail" persists as a historical joke about how hard it used to be, and building it in Grid is a satisfying demonstration of how far CSS layout has come — what once required a famous workaround now requires a readable template and five area names.

The responsive version of the holy grail is where Grid's areas again prove their value. On a narrow screen, the three columns stack into a single column, and the order of stacking is a design decision expressed in the drawing: main content first, then navigation, then aside; or navigation first, then content, then aside; or any other order the content's importance suggests. The drawing for the stacked version lists each area on its own row, in the chosen order, and the column template becomes a single flexible column. No item declaration changes, no HTML restructuring is needed, and the layout transforms from three columns to one stack with a single media query. That ease of transformation is why the holy grail, once a byword for difficulty, is now a beginner exercise in Grid — the tool finally matches the problem.

There is a historical footnote worth knowing, because it explains why the holy grail matters beyond itself. The layout's difficulty under floats was not just a technical curiosity; it shaped how the web looked for years, pushing designers toward simpler layouts that floats could handle, and away from three-column designs that users might have preferred. Grid removed that constraint, and the layouts that followed — dashboards, three-column articles, complex magazine pages — became possible without workarounds. Building the holy grail in Grid is therefore not just an exercise in a template; it is a small piece of web history, demonstrating that a layout tool's limitations shape design, and that a better tool expands what designers can imagine. When you build it, notice how little code it takes, and remember that the same layout once filled articles, books, and conference talks with its difficulty.

.page {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  grid-template-areas:
    "header header header"
    "nav main aside"
    "footer footer footer";
}

The holy grail layout also makes a good test case for learning Grid, because it can be built in two different ways and the comparison is instructive. Building it with areas produces a readable drawing that shows the page shape at a glance. Building it with line numbers produces a more explicit set of placements that shows exactly which lines each region spans.

Check: What were the hardest features of the holy grail layout before Grid existed?

Section 41 — A note on masonry layoutsA masonry layout — the staggered, brick-like arrangement where items of different heights fill columns without aligning into rows, as popularised by the site Masonry and by image boards — is not something CSS Grid produces by itself, despite frequent requests for it. Grid's rows align: items in a row share the row's height, and the next row starts below the tallest item in the row above, which produces a regular grid, not a staggered one. A true masonry layout, where each column is independent and items stack tightly within their column regardless of other columns' heights, requires either a column-based technique — CSS multi-column layout, which stacks items down each column but orders them column by column rather than row by row — or JavaScript that measures items and places them, or the newer CSS masonry feature that has been proposed for Grid but is not yet a standard, stable part of the language. Knowing that Grid does not do masonry prevents a frustrating search for a Grid property that does not exist.

The distinction between Grid's regular rows and masonry's staggered columns is worth understanding, because the two look similar at a glance but behave differently. In a Grid gallery, a tall card makes its whole row tall, leaving empty space below the shorter cards in that row. In a masonry gallery, the shorter cards' columns continue immediately below them, with the next item filling the space, so there is no row-level empty space, only the natural variation of column heights. That tight packing is masonry's appeal for image galleries, where images have varied aspect ratios and the design wants no wasted space. But it comes at a cost: masonry's columns are independent, so items do not align across columns, and the reading order becomes column-based rather than row-based, which affects how users scan the gallery and how assistive technology presents it. The trade-off — density versus alignment and order — is the same family as the dense packing trade-off in Section 38, expressed at a larger scale.

For most projects, the honest answer to "how do I build masonry?" is to ask whether masonry is genuinely needed, or whether a Grid gallery with well-chosen row sizing would serve just as well. A gallery where images share an aspect ratio, or where cards are designed to a consistent height, looks better as a regular grid than as masonry, because the regularity is part of the design. A gallery where images vary wildly and the design celebrates that variety may genuinely want masonry, in which case the practical options are multi-column layout for a simple, CSS-only version — with its column-based ordering — or a JavaScript library for a row-ordered version, with the added complexity and loading considerations that JavaScript brings. Either way, the decision should be deliberate, weighing density against order and complexity, rather than assuming Grid will provide masonry for free.

/* Grid rows align; masonry columns do not. Different layout modes. */

Check: Why does CSS Grid not produce a true masonry layout by itself?

Section 42 — Responsive grids with media queriesResponsive grids use media queries for structural changes, while the auto-fit pattern handles column-count changes without them, and the two together cover the full responsive range. A media query that rewrites the areas drawing at a breakpoint transforms a page from three columns to one stack; a media query that changes a dashboard's template from four columns to two reorganises its widgets; a media query that adjusts gaps, font sizes, or row heights refines the layout for a new width. The principle from Chapter 9 applies: breakpoints should be chosen where the layout breaks, not from a device list, and each media query should change as little as possible — one template, one drawing, one gap — so that the responsive stylesheet remains readable and each breakpoint's reason is visible.

A common responsive pattern for a dashboard or gallery is to define the grid for the wide layout first, then add media queries that reduce the column count as the width falls: four columns above 1000 pixels, two columns below it, one column below 600. With the auto-fit pattern, that reduction happens automatically and the media queries may be unnecessary for the gallery itself, but for a dashboard with explicitly placed widgets, the media queries are essential, because the widgets' placements must change when the column count changes — a widget spanning columns 3 to 5 in a four-column grid cannot keep that placement in a two-column grid, and its placement must be restated for the new template. That restatement is the responsive work for explicit grids, and it is where named lines and areas earn their keep: placements by name survive template changes better than placements by number, so a dashboard placed by areas may need only a new drawing at each breakpoint, while one placed by numbers needs new placements for every widget.

Testing responsive grids requires the same discipline as testing responsive flex layouts: resize slowly through the widths, watch for the points where the layout breaks or looks awkward, and add or adjust a breakpoint there. Pay particular attention to the widths where the column count changes in an auto-fit grid, because those are the widths where cards jump from one count to another and may look stretched or cramped just before the change. And test with real content, not just placeholder boxes, because real content — long headings, wide images, varied text lengths — is what reveals sizing problems that placeholders hide. A responsive grid that works with real content at every width, from a phone to a wide monitor, is a grid that is finished; one that works only with placeholders at a few widths is a grid that is still waiting for its breakpoints to be chosen by its content.

.dashboard {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

@media (max-width: 900px) {
  .dashboard {
    grid-template-columns: repeat(2, 1fr);
  }
}

Check: For a dashboard with explicitly placed widgets, why are media queries still needed even though auto-fit grids are not?

Section 43 — Empty cells and sparse placementEmpty cells are a normal part of Grid, not an error: they arise when a grid has more cells than items, when an explicitly placed item skips cells, or when a spanning item leaves a hole that sparse packing does not fill. An empty cell is simply a cell with no item in it, and it behaves like any other cell for track sizing — it contributes its track's size but no content — so a row with an empty cell still has that cell's width, and the grid's structure remains regular even where items are missing. That regularity is a strength: a dashboard with a missing widget still shows the grid's shape, and a gallery with a hole still aligns its remaining items, because the tracks exist independently of the items that occupy them. Empty cells are the grid's way of preserving structure when content is absent.

Sparse placement, the default, preserves document order by leaving holes rather than pulling later items forward, as Section 38 explained. That means a grid with a hole will show the hole, and the items after the hole will appear in their document positions, not shifted to fill it. For layouts where order matters — a sequence of steps, a ranked list, a form's fields — that preservation is correct, and the hole is the honest visual result of a missing item. For layouts where density matters — a photo gallery, a product listing — the hole may look like a mistake, and dense packing is the remedy, trading order for fullness. The choice between sparse and dense is therefore a choice about what the empty cell means: in a sparse grid, it means "nothing belongs here, by order"; in a dense grid, it means "nothing fits here yet, but something smaller might".

A practical point about empty cells and track sizing is that they do not affect content-based sizing: an empty cell contributes no content, so a content-sized track's size is determined by its occupied cells only, and a row of empty cells will collapse to its minimum or to the size set by auto-rows, rather than to a content height. That can produce a row of zero height if all its cells are empty and no row size is set, which is usually harmless — an empty row takes no space — but can surprise you if you expected the row to reserve space for a future item. Setting an explicit row size or an auto-rows minimum reserves that space, making the empty row visible as a gap, which is the right approach when the row's space should be held for content that will arrive later, such as a widget loaded asynchronously. Empty cells are thus not just absences; they are places in the structure, and their sizing follows the same rules as occupied cells, minus the content.

.page {
  display: grid;
  grid-template-areas:
    "header header"
    "sidebar main"
    ". footer";
}

Check: In a sparse grid, why does a hole left by a spanning item stay empty?

Section 44 — Debugging Grid in developer toolsBrowser developer tools can show grid containers with an overlay: the container gets a badge, and inspecting it draws the grid lines, numbers them, shades the tracks, and labels the named areas. When a layout looks wrong, inspect the container first and check the structure — how many columns and rows there are, where the lines fall, which cells are occupied and which are empty — because most Grid confusion is structural: a miscounted line, a missing track, an item placed in the wrong cell, or a template that does not match the drawing. The overlay turns the abstract template into a visible diagram, and seeing the diagram resolves structural problems faster than re-reading the stylesheet, just as the flex overlay resolves axis problems in Chapter 9.

A systematic debugging routine for Grid mirrors the flex routine but at the structural level. First, confirm the container: is display: grid applied, and to the element you think? Second, confirm the template: how many columns and rows does the overlay show, and do they match the template you wrote? Third, confirm the placements: where does the overlay show each item, and does that match where you intended it? Fourth, check the sizing: are the tracks the sizes you expected, or is a content minimum, a fixed size, or a fraction distorting them? Fifth, check for implicit tracks: are there extra rows or columns the template did not define, created by an item placed beyond the template or by more items than the template provides? Working through those five checks in order resolves the great majority of Grid problems, and each check corresponds to a section of this chapter, which is why the chapter is organised from structure to placement to sizing.

Finally, remember that developer tools show you the grid, not the design. They can tell you that a track is 300 pixels wide, but not whether you wanted it to be 300 pixels wide — that judgement comes from the design, the sketch, or the specification you are implementing. Debugging is therefore a conversation between the tools and the design: the tools report the structure, the design says what the structure should be, and the gap between them is the bug. Keeping a sketch of the intended grid beside the browser while you debug makes that conversation concrete, and it prevents the trap of adjusting values until the layout "looks about right" without knowing why. A grid that is right for a reason stays right when content changes; a grid that is right by accident breaks at the next content change. Use the tools to find the reason, fix the reason, and the grid will stay fixed.

/* If an item is in the wrong cell, check: template? lines? placement? */

Check: What is the first thing to check in developer tools when a grid item is in the wrong place?

Section 45 — Source order and accessibility in GridGrid's placement powers — areas, lines, spans, and dense packing — all separate visual order from document order, and that separation carries the same accessibility responsibility as Flexbox's reordering. Screen readers and keyboard users experience the document order, not the visual grid, so a page whose visual layout puts the main content first but whose HTML puts the navigation first will be experienced differently by different users. That difference is sometimes intentional and beneficial — main content first in the document helps screen reader users reach it without passing through navigation on every page — but it must be deliberate, and it must be tested, because an unintentional mismatch confuses the users who rely on document order to understand the page.

The guideline is the same as in Chapter 9, extended to two dimensions: make the document order meaningful on its own, and use Grid placement to present that meaning well, not to contradict it. A page whose HTML order is header, navigation, main, aside, footer is meaningful as a document, and a grid that presents those regions in a different visual arrangement — sidebar on the right instead of the left, aside below the main content on a phone — is presenting the same meaning in a different visual form, which is legitimate. A page whose HTML order is jumbled — footer content in the middle of the document because its grid placement puts it at the bottom visually — is not meaningful on its own, and no amount of visual polish fixes the underlying confusion for users who cannot see the visual arrangement. The document is the foundation; the grid is the presentation; and the presentation should serve the foundation, not override it.

Testing for order mismatches in a grid follows the same routine as for flex: tab through the page and check that focus moves in a sequence that makes sense visually; run a screen reader over the page and listen to the order of announcements; and view the page with styles disabled, to see whether the document order alone tells a coherent story. If any of those checks reveals a confusing sequence, the fix is usually to reorder the HTML to match the visual intent, or to simplify the grid placement so the two orders agree. Grid gives you extraordinary power to arrange content visually, and with that power comes the responsibility to ensure that the arrangement serves every user, not just those who see it. A grid that is beautiful visually and coherent in document order is a grid that is finished; one that is beautiful only visually is a grid that is still waiting for its order to be considered.

<!-- Document order should make sense on its own: header, nav, main, aside, footer. -->
<div class="page">
  <header>Header</header>
  <nav>Navigation</nav>
  <main>Main content</main>
  <aside>Aside</aside>
  <footer>Footer</footer>
</div>

Check: Why does Grid's ability to reorder items visually create an accessibility responsibility?

Section 46 — Equal-height rowsGrid rows give you equal heights by structure: all items in a row share the row's height, because the row is a track and the items stretch to fill their cells by default. That is a stronger guarantee than Flexbox's equal heights, which apply within a flex line but not across lines — in a wrapping flex row, each line's height is independent, while in a Grid, each row's height is a property of the grid, and items in that row share it by definition. For a gallery where every card in a row should be the same height, Grid delivers that equality without any item-level declarations, because the equality comes from the track, not from the items agreeing to stretch. The distinction matters for galleries with many rows, where flex lines would vary but grid rows can be made uniform with a single auto-rows declaration.

The row height itself is determined by the tallest item in the row, unless an explicit row size overrides it. That means a row containing one long article and three short notices will be as tall as the article, and the notices will stretch to fill it — usually the desired result for a card gallery, where uniform heights look tidy. But it also means a single very tall item can make its whole row tall, leaving large empty spaces in the shorter cards, which may look worse than varied heights would. The remedy is to control the row height explicitly: a fixed auto-rows height makes every row the same, regardless of content, with tall content either clipped inside its card or handled by the card's internal scrolling; a minmax auto-rows range sets a floor and lets taller content grow its row, balancing uniformity with content needs. The choice is a design judgement about whether uniformity or content fidelity matters more for that gallery.

Equal heights across all rows — not just within each row — is a further step, achieved by giving every row the same size, either through an explicit template with equal rows or through a uniform auto-rows value. A gallery with grid-auto-rows: 300px has every row 300 pixels tall, so every card in the gallery shares a height, not just the cards in its row. That uniformity produces the strict, magazine-like grid that many designs want, but it requires the cards' contents to fit 300 pixels, or to be designed to handle overflow gracefully — text that wraps within the height, images cropped to a frame, buttons positioned within the card. If the content cannot be guaranteed to fit, a minmax range is safer than a fixed height, because it lets exceptional rows grow rather than clipping their content, at the cost of breaking strict uniformity for those rows. The trade-off between strictness and safety is the same one that runs through all of Grid sizing, and it should be decided per gallery, not by habit.

.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 300px;
}

Check: How does Grid achieve equal heights differently from Flexbox?

Section 47 — Centring with GridGrid centres content in two ways, at two levels, and distinguishing them is the key to using Grid for centring. At the track level, `justify-content` and `align-content` centre the whole grid within its container, when the grid is smaller than the container. At the item level, `justify-items` and `align-items` — or the `place-items` shorthand — centre items within their cells, when the items are smaller than their cells. And at the single-item level, `place-self: center` centres one item within its cell. Each level answers a different centring question: where does the grid sit, where do the items sit in their cells, and where does this one item sit in its cell? Answering the wrong level's question produces a layout that looks centred in one sense but not in the sense the design wanted.

The simplest Grid centring pattern is a single-cell grid: a container with display: grid and place-items: center, containing one item, centres that item both horizontally and vertically within the container, just like the flex dead-centre pattern. The Grid version is arguably even simpler, because it needs only one property — place-items: center — rather than two, and it works for any container size and any item size, adapting as content changes. It is the standard Grid answer to the hero section, the empty state, and the modal panel, and it is worth knowing alongside the flex version, because either tool centres a single item well, and the choice between them for a single item is a matter of what else the container does — if the container will later hold a grid, use Grid; if it will hold a line, use Flexbox.

Centring a group of items in a grid is a different task, and it uses the content alignment properties: a grid of panels inside a large container, centred as a group, uses justify-content: center and align-content: center to position the tracks, while the items within their cells follow the item alignment. That two-level centring — tracks centred in the container, items centred in their cells — can produce a fully centred grid where both the grid and its contents are centred, which is a striking and useful layout for dashboards, galleries, and control panels that should float in the middle of a large area. The precondition, as always, is spare space: the container must be larger than the grid for track centring to be visible, and the cells must be larger than the items for item centring to be visible, and both preconditions should be checked when a centred grid does not look centred.

.hero {
  display: grid;
  place-items: center;
}

Centring with Grid also helps when the item itself contains text that should be centred. A single cell grid with centred items will centre the box, but the text inside the box follows its own alignment rules, so a left aligned paragraph inside a centred card will still look left aligned within the card. That is correct behaviour, because the grid centres the item, not the text inside it.

Check: What is the difference between centring grid tracks and centring items within their cells?

Section 48 — Common Grid mistakesThe classics are worth listing together, because recognising them by name makes them faster to fix. First, counting lines as columns: a three-column grid has four lines, so `1 / 3` covers two columns, not three, and the full span is `1 / 4` or `1 / -1`. Second, setting placement properties on elements that are not grid items — grandchildren, not direct children — and wondering why nothing moves. Third, expecting `justify-content` to move items within their cells, when it moves tracks within the container, and vice versa for `justify-items`. Fourth, forgetting that fractional tracks need a definite container size to share, so a `1fr` row in a container of indefinite height collapses to content size. Fifth, using `repeat(auto-fit, 1fr)` without a minmax minimum, which cannot calculate how many tracks fit. Each of these has a section in this chapter, and each is fixed by returning to the model: tracks, lines, cells, and areas, and the distinction between structure, placement, and sizing.

Beyond those five, there are subtler mistakes that appear as grids grow. One is defining a template that does not match the areas drawing — a drawing with three columns but a template with two, for example, which produces an invalid grid and silently falls back to automatic placement, leaving items in unexpected cells. The drawing and the template must agree on the number of columns and rows, and checking that agreement is the first step when an areas layout collapses. Another is overusing explicit placement: placing every item by line numbers in a gallery whose item count varies, so that adding an item requires renumbering everything, when automatic placement would have handled the growth. Another is setting fixed row heights that clip content, when a minmax range would have protected it. And another is mixing gap with margins on items, producing double spacing, when gap alone would have sufficed — the same mistake as in Flexbox, repeated at the grid level.

The deepest common mistake, as in Chapter 9, is not a property error but a thinking error: treating Grid as a set of templates to copy rather than a structure to design. Developers who copy a holy grail template or an auto-fit gallery without understanding tracks, lines, and placement will be stuck whenever their layout differs from the template they copied, because they cannot derive a new template from the model. Developers who understand the model — structure defined by the container, items placed into cells, sizing by tracks — can build any grid from first principles, because the model generates the template for any layout: count the columns, size them, name the areas or lines, place the items, and the grid follows. That is why this chapter spent fifty sections on the model rather than presenting a cookbook of templates. Templates are useful, but they are outputs of understanding, not substitutes for it.

/* Grid mistakes checklist: lines? container? level? size? minmax? */

Check: If a grid item spans fewer columns than you expected, what is the most likely counting error?

Section 49 — Grid and Flexbox togetherGrid and Flexbox together are the complete layout toolkit for most front-end work, and the combination is not just a matter of using both on the same page but of using each at the level where its model fits. A page is a Grid of regions; each region is a Flexbox arrangement of its contents, or a nested Grid if its contents need two dimensions; each component inside a region is a Flexbox line or a Grid structure, according to its own shape. That nesting — Grid, then Flexbox, then Grid again, as needed — produces layouts that are both structured and flexible, because each level does what it does best, and no level is forced to imitate another level's model. The result is code that reads clearly: the page's template shows its structure, the header's flex row shows its line, the card's flex column shows its stack, and each declaration's purpose is visible from its level.

A concrete example makes the combination tangible. Consider a dashboard page: the page is a Grid with a header, a sidebar, and a main area. The header is a Flexbox row with a logo, navigation, and actions. The sidebar is a Flexbox column of link groups. The main area is a Grid of widgets, each widget placed by lines or areas. Each widget is a Flexbox column with a title, a chart, and a footer, or a Grid of statistics if its interior needs columns. The page has four levels, alternating between Grid and Flexbox, and each level's template or flex properties describe only that level's arrangement. Changing the header's navigation does not affect the main area's widgets; changing a widget's interior does not affect the page's structure; and the whole page remains maintainable because each level is independent. That independence is the payoff for nesting thoughtfully rather than flattening everything into one giant grid or one giant flex container.

There are also cases where the combination happens within a single component, not just across levels. A card that needs its image and text side by side, with a footer spanning both, is a Grid of two columns and two rows inside a Flexbox row of cards — Grid for the card's interior, Flexbox for the row. A toolbar that needs a group of buttons aligned as a grid of icons, within a flex row of groups, is the reverse: Flexbox for the toolbar, Grid for the icon group. Those within-component combinations are less common than the across-level ones, but they follow the same principle: choose the tool per arrangement, not per page, and let a component use Grid for its two-dimensional parts and Flexbox for its one-dimensional parts, even within the same component. The tools are not rivals competing for the page; they are collaborators, each handling the arrangements it was designed for.

/* Page: Grid. Header: Flexbox. Main: Grid. Card: Flexbox. */

Check: Why is it normal for a page to use Grid at one level and Flexbox at another?

Section 50 — Grid in one paragraphGrid turns a container into a structure of rows and columns, defined by templates, gapped by design, and filled by items placed into cells by name, by line, or by automatic flow — with sizing by fixed lengths, fractions, content, and minmax ranges, and alignment at the track level and the item level. That sentence is the whole chapter in miniature, but a summary deserves more than a sentence, because the value of this chapter is in understanding each of its clauses well enough to use them independently. The structure-first model is the foundation: the container defines the tracks, and the items conform to them, which is what gives Grid its alignment power — columns shared across rows, rows shared across columns, and a regularity that content-driven layouts cannot achieve. The vocabulary is the second foundation: tracks, cells, lines, and areas, four words that name every part of the structure and every property that manipulates it, from template definitions to placement declarations.

The sizing story is the third foundation, and it repays revisiting. Fixed sizes state intent, fractions share free space, content sizes let content decide, minmax bounds the result, and auto-fit with minmax makes the column count itself responsive, without a media query. Those sizing tools, combined with the placement tools — areas for page skeletons, lines for dashboards, spans for featured items, flow for dynamic content, dense packing for galleries — cover nearly every two-dimensional layout you will build, from a simple gallery to a complex dashboard. And the alignment system, with its two levels and its consistent values, covers every positioning need within that structure, from centring a grid in its container to aligning a single badge in its cell. The foundations are few, but their combinations are many, and that combinatorial power is why Grid feels both simple to learn and deep to master.

The cautions of the chapter deserve a place in the summary too, because they are what separate a working grid from a good one. Visual order is not reading order, so placement and dense packing demand care and testing. Content minimum sizes protect readability, so overflows should be answered by addressing content or sizing, not by forcing tracks. Spare space is a precondition for alignment, so properties that seem broken are often simply without room to act. And Grid itself has a boundary: it does not do masonry, and it does not replace Flexbox for one-dimensional lines.

As you move to the next chapter, carry one exercise with you: build the practical task below, then rebuild its page skeleton in Flexbox, and compare the two versions. Notice where Grid felt natural — the aligned columns, the spanning header, the areas drawing that reads like a sketch — and where Flexbox would have felt more natural, such as the header's internal row or the cards' internal stacks. That comparison is the real lesson of these two chapters together: not that one tool is better, but that each tool has a shape of problem it fits, and a developer who knows both shapes can choose well, level by level, throughout a page.

Check: In your own words, what are the three ways a grid item can be placed?

Practical task

Build a small dashboard page using only CSS Grid for the page structure. It must have a header spanning the full width, a sidebar on the left, a main area on the right containing a grid of six cards, and a footer spanning the full width. Use grid-template-areas for the page skeleton, an auto-fit card grid inside the main area, and gap for all gutters. Then add a media query that stacks the sidebar below the main area on a narrow screen by rewriting only the areas drawing and the column template. Use Inspect to confirm the grid lines and areas.

Chapter questions

  1. Explain the difference between justify-items and justify-content in a grid.
  2. A gallery must show as many columns as fit, each at least 220 pixels wide. Write the grid-template-columns declaration and explain how it works.
  3. Write the CSS for a page with a header, sidebar, main area and footer using grid-template-areas, and explain what repeating a name does.
  4. An item placed with grid-column: 1 / 3 covers only two columns in a four-column grid. Explain why, and write the declaration for all four columns.
  5. When would you choose Flexbox over Grid for a row of cards, and when would you choose Grid over Flexbox?