Flexbox lays items out in one direction — a row or a column — and distributes free space between them; it suits headers, menus and button rows. Grid works with columns and rows at once, so it fits card grids and page layouts. One line gives you a responsive card grid without media queries: grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)).
After this you will be able to
- Explain the difference between
content-boxandborder-boxand calculate a block's final width - Build a Flexbox header with
gap,align-items,flex-wrapand an auto margin - Create a responsive card grid with
repeat(auto-fit, minmax(min(100%, 240px), 1fr))and a sidebar layout with Grid - Find and fix column overflow with
minmax(0, 1fr)andmin-width: 0
Verified with
CSS Flexible Box Layout Level 1CSS Grid Layout Level 1Microsoft Edge 155 + Playwright 1.64W3C CSS Validator (css3svg)
Checked against docs: October 11, 2026
Contents
- The box model and box-sizing: what makes up a block's width
- Flexbox: axes, alignment, gap and wrapping
- Grid: columns, fr, repeat, minmax and auto-fit
- When to use Flex and when to use Grid
- Practice: a page of three blocks
- Responsive without extra media queries
- Common mistakes: overflow, min-width: 0, percentages
- Exercises and next steps
The box model and box-sizing: what makes up a block's width
Before laying blocks out you need to know how big they are. Every element in CSS is a rectangle of four layers: content, padding, border and margin. By default (box-sizing: content-box) the width property sets the width of the content only, and padding and border are added on top.
/* width and height include padding and border */
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
line-height: 1.5;
}
.container {
max-width: 1200px;
margin-inline: auto;
padding-inline: 16px;
}We measured two identical blocks in Edge with width: 300px, padding: 20px and a 2 px border. With content-box the block came out 344 px wide (300 + 2 × 20 + 2 × 2), with border-box exactly 300 px. MDN recommends border-box for layout: sizes become predictable, and width: 100% with padding no longer spills out of its parent. That is why box-sizing: border-box for all elements comes first in almost every project.
Flexbox: axes, alignment, gap and wrapping
Flexbox is switched on with a single display: flex on the parent. The parent becomes a flex container and its direct children become flex items. Everything in Flexbox is measured along two axes: the main axis, set by flex-direction, and the cross axis, perpendicular to it. The default is flex-direction: row, so the main axis runs along the line.
- Items sit in a row and start at the beginning of the main axis.
- They do not grow along the main axis (
flex-grow: 0) but may shrink (flex-shrink: 1). - On the cross axis they stretch to the full height of the row (
align-items: stretch). - They do not wrap (
flex-wrap: nowrap): when there is not enough room they overflow the container.
flex-direction
- Where
- container
- What it does
- Direction of the main axis:
row,columnand their-reversevariants
justify-content
- Where
- container
- What it does
- Distribution along the main axis:
flex-start,center,space-between…
align-items
- Where
- container
- What it does
- Alignment on the cross axis:
stretch,center,flex-start…
gap
- Where
- container
- What it does
- Space between items — no margin on each item and no extra space at the edges
flex-wrap
- Where
- container
- What it does
wrapallows items to move to a new line
flex
- Where
- item
- What it does
- Shorthand for
flex-grow flex-shrink flex-basis:flex: 1=1 1 0,flex: auto=1 1 auto
A site header is the classic Flexbox job: logo, menu and button in one row, centred vertically.
/* Header: a single row, Flexbox */
.site-header {
display: flex;
flex-wrap: wrap; /* on a narrow screen the menu moves to a new line */
align-items: center; /* alignment on the cross axis */
gap: 12px 24px; /* rows, columns */
padding: 12px 16px;
border-bottom: 1px solid #ddd;
}
.menu {
display: flex;
gap: 16px;
}
.cta {
margin-inline-start: auto; /* pushes the button to the right edge */
padding: 8px 16px;
border-radius: 8px;
background: #1f2937;
color: #fff;
text-decoration: none;
}align-items: centerlines up the logo, menu and button on their vertical centres even though the button has its own padding.gap: 12px 24pxsets the space between rows and between columns, and it keeps working after wrapping.margin-inline-start: autoon the button takes all the free space before it and pushes it to the right edge. MDN describes auto margins as a way to separate an item or a group from the rest.flex-wrap: wrapis the safety net for narrow screens: at 375 px our header reflowed into two rows without a single media query, and at 768 and 1280 px it stayed on one line.
Grid: columns, fr, repeat, minmax and auto-fit
Grid is a two-dimensional layout: you describe columns (and rows if needed) on the container, and items place themselves into cells. Columns are set with grid-template-columns. Besides pixels and percentages Grid has the fr unit, a fraction of the free space: 1fr 1fr 1fr gives three equal columns, 2fr 1fr 1fr splits the space into four parts and gives two to the first column. Repeats are written with repeat(): repeat(3, 1fr) is the same as 1fr 1fr 1fr.
The most useful Grid pattern is a grid that decides by itself how many columns fit:
/* Card grid: Grid decides how many columns fit */
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
gap: 16px;
padding: 0;
list-style: none;
}
.card {
padding: 16px;
border: 1px solid #ddd;
border-radius: 12px;
}
.card h2 {
margin: 0 0 8px;
font-size: 1.125rem;
}minmax(240px, 1fr)
A column is at least 240 px wide, and whatever is left is shared equally between columns.
repeat(auto-fit, …)
The browser creates as many columns as fit. Unlike
auto-fill,auto-fitcollapses empty columns, so the cards stretch across the full width.min(100%, 240px)
A safety net for containers narrower than 240 px: the column minimum can never exceed the container, so a card does not overflow.
375 px
- Card columns
- 1
- Card width
- 343 px
768 px
- Card columns
- 2
- Card width
- 360 px
1280 px
- Card columns
- 4
- Card width
- 280 px
We checked the difference between auto-fit and auto-fill too: two cards in a 1168 px container got 576 px each with auto-fit and 280 px each with auto-fill, because empty columns remained on the right. And without min(100%, 240px) a card in a 200 px container kept its 240 px width and overflowed; with it, the card shrank to 200 px.
When to use Flex and when to use Grid
MDN states the main difference like this: Flexbox was designed for layout in one dimension — a row or a column — and Grid for two dimensions at once. The same page gives a second test: Flexbox works from the content out, so items are sized by their content, while Grid works from the layout in — you describe the grid and items are placed into it. If items in the second row of a wrapping flex container must line up exactly under the first row, that is a Grid job: wrapped flex lines are independent and distribute space on their own.
Flexbox
- Header, menu, a row of buttons, tags
- Centring a single element
- Item size depends on its content
- Pushing an item to an edge with
margin: auto
Grid
- Card grid, gallery, pricing plans
- Page skeleton: content and sidebar
- Columns and rows must line up
- Responsiveness through
auto-fitandminmax()without media queries
Practice: a page of three blocks
Let's put it together: a Flexbox header, a grid of six cards and a "content + sidebar" block. The markup is semantic, as in the HTML lesson; the CSS is split into the files you have already seen.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Coffee machine repair — Zerno service</title>
<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="header.css">
<link rel="stylesheet" href="cards.css">
<link rel="stylesheet" href="layout.css">
</head>
<body>
<header class="site-header">
<a class="logo" href="/">Zerno</a>
<nav class="menu" aria-label="Main menu">
<a href="#services">Services</a>
<a href="#prices">Prices</a>
<a href="#faq">FAQ</a>
</nav>
<a class="cta" href="#request">Request a repair</a>
</header>
<main class="container">
<section id="services">
<h1>Coffee machine repair</h1>
<ul class="cards">
<li class="card"><h2>Diagnostics</h2><p>We find the cause and agree on the cost.</p></li>
<li class="card"><h2>Descaling</h2><p>We remove scale and replace filters.</p></li>
<li class="card"><h2>Pump repair</h2><p>We restore water pressure.</p></li>
<li class="card"><h2>Gaskets</h2><p>We replace group head gaskets.</p></li>
<li class="card"><h2>Electronics</h2><p>We repair boards and sensors.</p></li>
<li class="card"><h2>Maintenance</h2><p>Scheduled visits for coffee shops.</p></li>
</ul>
</section>
<div class="with-sidebar">
<article class="content" id="faq">
<h2>How a repair works</h2>
<p>A technician comes over, runs diagnostics and names the cost before any work starts.</p>
<pre><code>Request status: https://example.com/requests/2026/10/11/very-long-tracking-identifier-0001</code></pre>
</article>
<aside class="sidebar" id="prices">
<h2>Prices</h2>
<p>Diagnostics are free if you order the repair.</p>
</aside>
</div>
</main>
</body>
</html>/* Content and sidebar: stacked on phones, two columns from 768px */
.with-sidebar {
display: grid;
grid-template-columns: minmax(0, 1fr); /* one column that does not grow with its content */
gap: 24px;
margin-block: 32px;
}
@media (min-width: 768px) {
.with-sidebar {
grid-template-columns: minmax(0, 1fr) 280px;
}
}
.content pre {
overflow-x: auto; /* a long line scrolls inside the block */
padding: 12px;
background: #f3f4f6;
}
.sidebar {
padding: 16px;
border-radius: 12px;
background: #fef3c7;
}How to check the result. Open the page in Chrome or Edge, open DevTools and resize the window. In the Elements panel grid and flex badges appear next to the containers; clicking a badge draws the grid lines and flex item boundaries on the page. We checked the same programmatically with getBoundingClientRect() at three widths:
375 px
- Header
- 2 rows
- Cards
- 1 column
- Sidebar
- below the content
- Horizontal scrolling
- none
768 px
- Header
- 1 row
- Cards
- 2 columns
- Sidebar
- on the right, 280 px
- Horizontal scrolling
- none
1280 px
- Header
- 1 row
- Cards
- 4 columns
- Sidebar
- on the right, 280 px
- Horizontal scrolling
- none
Responsive without extra media queries
A media query (@media) applies rules at certain window widths. You need one when the layout really changes structure, like a sidebar that moves below the content on phones. But Flexbox and Grid handle most reflows by themselves as long as fixed widths don't get in the way:
flex-wrap: wrapmoves items to a new line when they run out of room, so the header needs no breakpoint of its own.repeat(auto-fit, minmax(…))works out the number of columns, so the card grid needs no four media queries for each screen.max-widthinstead ofwidthon the container: on a wide screen it is capped at 1200 px, on a narrow one it takes the full width.frunits and percentages instead of pixels for columns,gapinstead ofmarginon each item.
Breakpoints and mobile-first from the design side are covered in the article on responsive design.
Common mistakes: overflow, min-width: 0, percentages
Three mistakes come up more often than the rest. We reproduced all three on a separate page with a 760 px container:
.demo { width: 760px; margin-bottom: 32px; outline: 1px dashed #999; }
/* 1. box-sizing: same width, different result */
.box { width: 300px; padding: 20px; border: 2px solid; }
.box-content { box-sizing: content-box; } /* 300 + 2 × 20 + 2 × 2 = 344px */
.box-border { box-sizing: border-box; } /* exactly 300px */
/* 2. Percentages + gap: three 33.333% items do not fit in a row */
.row-percent { display: flex; flex-wrap: wrap; gap: 16px; }
.row-percent > * { width: 33.333%; }
.row-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
/* 3. Long content stretches a 1fr column */
.layout-bad { display: grid; grid-template-columns: 1fr 280px; gap: 24px; }
.layout-good { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 24px; }
/* the same in Flexbox: a flex item has min-width: auto */
.flex-bad { display: flex; gap: 24px; }
.flex-bad > .main { flex: 1; }
.flex-good { display: flex; gap: 24px; }
.flex-good > .main { flex: 1; min-width: 0; }
.flex-bad > .side, .flex-good > .side { flex: 0 0 280px; }
pre { overflow-x: auto; margin: 0; }Default content-box
- What we measured
width: 300pxproduced a 344 px box- Fix
box-sizing: border-box
Three 33.333% items with gap: 16px in Flexbox
- What we measured
- The third item wrapped to a second row: 100% of the width plus 32 px of gaps does not fit
- Fix
grid-template-columns: repeat(3, 1fr):frshares space aftergapis subtracted
A long line in a 1fr column
- What we measured
- The column stretched and the sidebar ended at 1006 px in a 760 px container
- Fix
minmax(0, 1fr)instead of1fr
A long line in a flex item with flex: 1
- What we measured
- The same overflow to 1006 px
- Fix
min-width: 0on the flex item
Why don't 1fr and flex: 1 shrink? The Flexbox specification gives flex items an automatic minimum size: by default min-width: auto, and the item does not get narrower than its content — the longest word, an image or a line in pre. Grid items behave the same way, and according to web.dev 1fr is minmax(auto, 1fr). An explicit minimum of 0 lifts the limit, and then overflow-x: auto on the pre scrolls the line inside the block without breaking the page.
One more warning, about order. Grid and the order property let you place an item anywhere visually, but MDN reminds us that screen readers read, and the Tab key moves, in HTML order. If the visual order differs a lot from the source, keyboard users will jump around the page. Change the order in the markup, not only in CSS.
Exercises and next steps
Next on the track is JavaScript in the browser: finding the elements of this page from code, changing them and reacting to clicks. If your project uses Tailwind CSS, the same flex, grid, gap and minmax are written as utilities there; the layout mechanics stay the same.
How this was verified5 · passedchecked 11 October 2026
“Run” means the code was executed in a scratch project or container and the output in the article comes from that run. “Partial” means it was checked against stubs instead of external services. “Not run” means it needs a device, an account or a key; the steps were checked against the official documentation. The verification project files and run logs are kept in the InterLAB archive.