CSS Grid: 10 Code-Along Examples

Learn CSS Grid by running it. Ten copy-and-paste examples, each with separate HTML and CSS, covering the fr unit, repeat, responsive auto-fit cards, spanning, named areas, the implicit grid and forms.

The Grid guide explains the theory: tracks, the fr unit, repeat(), named areas, spanning, and the two kinds of alignment. This workbook turns that into ten layouts you can run. Each example is two files, index.html and styles.css, kept separate so the markup stays clean and the styling lives in one place. Put both in the same folder, open the HTML, then edit the CSS to see what each property does. Where Flexbox thinks in one direction at a time, Grid lets you place things on rows and columns at once, so most of these examples are about two-dimensional structure.

1. A basic grid: three equal columns

display: grid plus grid-template-columns is the whole starting point. repeat(3, 1fr) makes three columns that each take an equal share of the row, and gap adds consistent spacing between every track.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="grid">
<div class="cell">One</div>
<div class="cell">Two</div>
<div class="cell">Three</div>
<div class="cell">Four</div>
<div class="cell">Five</div>
<div class="cell">Six</div>
</div>
</body>
</html>
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr); /* three equal columns */
gap: 10px; /* space between every cell */
font-family: sans-serif;
}
.cell {
background: #e8eef5;
padding: 1.5rem;
text-align: center;
border-radius: 6px;
}

Six items into three columns fall naturally into two rows. You never declared a second row; Grid created it for you. Change repeat(3, 1fr) to repeat(2, 1fr) and the same six items reflow into three rows.

2. The fr unit: columns in proportion

The fr unit divides the leftover space into shares. 1fr 2fr 1fr gives the middle column twice the width of each outer one, which is the bones of a content-with-rails layout.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="layout">
<div class="rail">Left rail</div>
<div class="main">Main content takes twice the width</div>
<div class="rail">Right rail</div>
</div>
</body>
</html>
.layout {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* middle column is twice as wide */
gap: 1rem;
font-family: sans-serif;
}
.rail { background: #d9e2ec; padding: 1rem; border-radius: 6px; }
.main { background: #f0f4f8; padding: 1rem; border-radius: 6px; }

Mix units freely. Try 240px 1fr 240px and the rails become fixed widths while only the middle flexes. fr always shares out whatever space is left after the fixed tracks are accounted for.

3. A responsive card grid with no media queries

This is the property combination people fall in love with. repeat(auto-fit, minmax(220px, 1fr)) fits as many columns as will hold a 220px minimum, then stretches them to fill the row. Resize the window and the column count changes by itself.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="cards">
<div class="card"><h3>Tracks</h3><p>Rows and columns.</p></div>
<div class="card"><h3>fr</h3><p>Proportional space.</p></div>
<div class="card"><h3>minmax</h3><p>A floor and a ceiling.</p></div>
<div class="card"><h3>auto-fit</h3><p>Columns that respond.</p></div>
<div class="card"><h3>gap</h3><p>Even spacing.</p></div>
</div>
</body>
</html>
body { background: #f0f4f8; padding: 1rem; }
.cards {
display: grid;
/* as many columns as fit, each at least 220px, sharing space equally */
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
font-family: sans-serif;
}
.card {
background: #ffffff;
border: 1px solid #d9e2ec;
border-radius: 8px;
padding: 1.25rem;
}

Swap auto-fit for auto-fill and watch the difference: auto-fit collapses empty tracks so the cards stretch to fill the row, while auto-fill keeps the empty tracks, leaving gaps on the right when there are few items.

4. Spanning an item across columns and rows

Individual items can break out of a single cell. grid-column: span 2 makes an item cover two columns; grid-row: span 2 does the same down the rows. This is how you build a feature tile inside an otherwise even grid.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="grid">
<div class="cell feature">Featured (spans 2 x 2)</div>
<div class="cell">A</div>
<div class="cell">B</div>
<div class="cell">C</div>
<div class="cell">D</div>
</div>
</body>
</html>
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 100px; /* height for any rows Grid creates */
gap: 10px;
font-family: sans-serif;
}
.cell {
background: #e8eef5;
display: grid;
place-items: center; /* centre the label in each cell */
border-radius: 6px;
}
.feature {
grid-column: span 2; /* cover two columns */
grid-row: span 2; /* cover two rows */
background: #1f4e78;
color: white;
}

You can also place items by line number, such as grid-column: 1 / 3, which means “start at line 1, end at line 3.” Spanning is the shorthand for when you care about size rather than exact position.

5. A whole page with named template areas

grid-template-areas lets you draw the layout as text. You name each region, paint the grid with those names, then assign each element to its area. The CSS reads like a picture of the page.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="page">
<header class="header">Header</header>
<aside class="sidebar">Sidebar</aside>
<main class="content">Content</main>
<footer class="footer">Footer</footer>
</div>
</body>
</html>
.page {
display: grid;
grid-template-columns: 200px 1fr; /* fixed sidebar, fluid content */
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
gap: 10px;
min-height: 300px;
font-family: sans-serif;
}
.header { grid-area: header; background: #1f4e78; color: white; }
.sidebar { grid-area: sidebar; background: #d9e2ec; }
.content { grid-area: content; background: #f0f4f8; }
.footer { grid-area: footer; background: #11243a; color: white; }
.header, .sidebar, .content, .footer { padding: 1rem; border-radius: 6px; }

The header and footer each repeat their name across both columns, so they stretch the full width. Naming areas makes intent obvious and lets you rearrange the whole layout by editing the template strings.

6. Reflowing that page layout for mobile

Named areas shine when you restack them at a breakpoint. The same regions get a new map inside a media query: one column, sidebar below the content. The HTML never changes.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="page">
<header class="header">Header</header>
<aside class="sidebar">Sidebar</aside>
<main class="content">Content</main>
<footer class="footer">Footer</footer>
</div>
</body>
</html>
.page {
display: grid;
gap: 10px;
font-family: sans-serif;
/* wide screens: sidebar beside content */
grid-template-columns: 200px 1fr;
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
}
@media (max-width: 600px) {
.page {
/* narrow screens: one column, content first */
grid-template-columns: 1fr;
grid-template-areas:
"header"
"content"
"sidebar"
"footer";
}
}
.header { grid-area: header; background: #1f4e78; color: white; }
.sidebar { grid-area: sidebar; background: #d9e2ec; }
.content { grid-area: content; background: #f0f4f8; }
.footer { grid-area: footer; background: #11243a; color: white; }
.header, .sidebar, .content, .footer { padding: 1rem; border-radius: 6px; }

Narrow the window past 600px and the layout restacks into a single column, with the content moved above the sidebar for a better reading order. That reordering is just a different area map.

7. Centre anything with place-items

Grid centres content in one line. place-items: center is shorthand for align-items: center and justify-items: center together, putting each item dead centre of its cell on both axes.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="stage">
<div class="badge">Centred with place-items</div>
</div>
</body>
</html>
.stage {
display: grid;
place-items: center; /* align-items + justify-items in one go */
height: 300px;
background: #f0f4f8;
}
.badge {
padding: 1.5rem 2rem;
background: #1f4e78;
color: white;
border-radius: 8px;
font-family: sans-serif;
}

There is a matching pair for positioning the whole grid inside a larger container, place-content, which combines align-contentand justify-content. Item alignment moves content within cells; content alignment moves the grid as a block.

8. The implicit grid and grid-auto-rows

You only have to define the columns. When items overflow into rows you never declared, Grid creates those rows automatically. grid-auto-rows controls how tall those implicit rows are.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="grid">
<div class="cell">1</div>
<div class="cell">2</div>
<div class="cell">3</div>
<div class="cell">4</div>
<div class="cell">5</div>
<div class="cell">6</div>
<div class="cell">7</div>
</div>
</body>
</html>
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr); /* only columns are explicit */
grid-auto-rows: 80px; /* height of each auto-created row */
gap: 10px;
font-family: sans-serif;
}
.cell {
background: #e8eef5;
display: grid;
place-items: center;
border-radius: 6px;
}

Add or remove .cell elements and the grid grows or shrinks by whole rows on its own, each one 80px tall. This is the difference between the explicit grid (what you declared) and the implicit grid (what Grid adds to fit the content).

9. A gallery that packs tightly with dense flow

Mix differently sized items and gaps can appear. grid-auto-flow: dense tells Grid to backfill those holes with later items that fit, producing a tighter, masonry-like pack.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="gallery">
<div class="tile wide">Wide</div>
<div class="tile">A</div>
<div class="tile tall">Tall</div>
<div class="tile">B</div>
<div class="tile">C</div>
<div class="tile wide">Wide</div>
<div class="tile">D</div>
</div>
</body>
</html>
.gallery {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 90px;
grid-auto-flow: dense; /* backfill gaps with items that fit */
gap: 8px;
font-family: sans-serif;
}
.tile {
background: #e8eef5;
display: grid;
place-items: center;
border-radius: 6px;
}
.wide { grid-column: span 2; background: #cfe0f1; }
.tall { grid-row: span 2; background: #1f4e78; color: white; }

Remove the word dense and you will see gaps open up where a wide tile would not fit. Add it back and Grid slots smaller tiles into those gaps. Be aware that dense packing can pull items out of source order, so keep it for galleries rather than ordered content.

10. A two-column form that lines up

Grid is excellent for forms because it keeps labels and inputs in tidy columns. A narrow column for labels, a wide one for fields, and the rows align automatically however long the labels are.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<form class="form">
<label for="name">Name</label>
<input id="name" type="text">
<label for="email">Email</label>
<input id="email" type="email">
<label for="msg">Message</label>
<textarea id="msg" rows="3"></textarea>
<button type="submit">Send</button>
</form>
</body>
</html>
.form {
display: grid;
grid-template-columns: auto 1fr; /* labels size to content, fields fill rest */
gap: 0.75rem 1rem; /* row gap, column gap */
align-items: center;
max-width: 480px;
font-family: sans-serif;
}
.form label { text-align: right; }
.form input,
.form textarea {
padding: 0.6rem;
border: 1px solid #b9c6d6;
border-radius: 6px;
}
.form button {
grid-column: 2; /* sit under the fields, not the labels */
justify-self: start; /* size to its label, do not stretch */
padding: 0.6rem 1.2rem;
border: 0;
border-radius: 6px;
background: #1f4e78;
color: white;
}

The labels column is auto, so it grows to fit the longest label, and the fields column takes the rest with 1fr. The submit button is pinned to the second column and told not to stretch, so it stays its natural width.

Work through these and you will have used every property from the main article, from fr and repeat() to named areas and the implicit grid. The fastest way to build intuition is to change one value at a time and predict the result before you reload. And remember the closing point from the guide: Grid and Flexbox are partners, not rivals. Reach for Grid to lay out the page in two dimensions, then drop a Flexbox container inside any cell to align what lives there.

As with the Flexbox workbook, keeping the CSS in its own styles.css file is the habit worth carrying into real projects. The stylesheet is cached, several pages can share it, and the markup stays readable, which is exactly what you want for anything you intend to keep.

See you soon.

View Comments (1)

Leave a Reply

Prev Next

Subscribe to My Newsletter

Subscribe to my email newsletter to get the latest posts delivered right to your email. Pure inspiration, zero spam.

Discover more from Discuss Data Science, Machine Learning and Analytics

Subscribe now to keep reading and get access to the full archive.

Continue reading