How to buildOctober 11, 20269 min read
Markup and layout: HTML and CSSStep 3 of 7

CSS layout: Flexbox and Grid on real blocks

In short

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)).

TutorialLevel: BeginnerCSSPractice: ~45–90 min

After this you will be able to

  • Explain the difference between content-box and border-box and calculate a block's final width
  • Build a Flexbox header with gap, align-items, flex-wrap and 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) and min-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
  1. 01The box model and box-sizing: what makes up a block's width
  2. 02Flexbox: axes, alignment, gap and wrapping
  3. 03Grid: columns, fr, repeat, minmax and auto-fit
  4. 04When to use Flex and when to use Grid
  5. 05Practice: a page of three blocks
  6. 06Responsive without extra media queries
  7. 07Common mistakes: overflow, min-width: 0, percentages
  8. 08Exercises 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.

base.css
/* 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;
}
Base styles of the demo page: box-sizing for every element and a centred container.

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, column and their -reverse variants

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
wrap allows 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.css
/* 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;
}
The header: one row on wide screens; on narrow ones the menu and button wrap.
  • align-items: center lines up the logo, menu and button on their vertical centres even though the button has its own padding.
  • gap: 12px 24px sets the space between rows and between columns, and it keeps working after wrapping.
  • margin-inline-start: auto on 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: wrap is 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:

cards.css
/* 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;
}
The service card grid: the number of columns depends on the container width, no media queries.
  1. minmax(240px, 1fr)

    A column is at least 240 px wide, and whatever is left is shared equally between columns.

  2. repeat(auto-fit, …)

    The browser creates as many columns as fit. Unlike auto-fill, auto-fit collapses empty columns, so the cards stretch across the full width.

  3. 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
Measured in Edge 155 on the demo page with a container up to 1200 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-fit and minmax() 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.

index.html
<!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>
The demo page: four CSS files linked in order; the long link in pre is there to test overflow.
layout.css
/* 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;
}
Content and sidebar: the only media query on the whole page.

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: wrap moves 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-width instead of width on the container: on a wide screen it is capped at 1200 px, on a narrow one it takes the full width.
  • fr units and percentages instead of pixels for columns, gap instead of margin on 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:

pitfalls.css
.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; }
Styles of the mistakes page: the broken and fixed versions side by side.

Default content-box

What we measured
width: 300px produced 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): fr shares space after gap is 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 of 1fr

A long line in a flex item with flex: 1

What we measured
The same overflow to 1006 px
Fix
min-width: 0 on 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.

Tell us about your task — we'll review your request and get back to you

Got a project?
We'll scope and estimate it — for free.

Tell us about the product or the problem. After reviewing the task we'll come back with an architecture, a timeline and a price for the first version.

  • Scope and risks estimated before we start
  • NDA — yours or ours
  • Work starts within a week

Please do not send passwords, access keys or source code through this form — we will agree a safe way to share access separately.

By clicking the button you agree to the processing of your personal data.