Step 04 of 21

Flexbox & Grid

Build navigation bars, card grids and full page layouts with Flexbox and CSS Grid, and know which one to reach for.

18 min 8 sections

Before Flexbox and Grid, CSS layout meant floats, clearfix hacks and fixed pixel widths. Today two purpose-built layout systems handle almost everything: Flexbox for arranging items along one line, and Grid for placing items in rows and columns at once. In this lesson you will learn how each one thinks, build the layouts you will use on nearly every project, and avoid the handful of gotchas that trip up everyone.

Normal flow, and why you need layout systems

Without any layout CSS, elements follow normal flow: block boxes stack vertically, each as wide as its container, and inline content wraps along lines of text. Normal flow is great for articles. It cannot put a logo on the left and links on the right, or arrange cards in three equal columns.

Both Flexbox and Grid are switched on the same way – by setting display on a container. Its direct children become items that the container arranges. Grandchildren are not affected, which is the first thing to check when a layout "isn't working".

css
.toolbar { display: flex; }   /* children become flex items */
.gallery { display: grid; }   /* children become grid items */

Flexbox: one dimension

A flex container lays its items out along a main axis. The cross axis runs perpendicular to it.

  • flex-direction: row (the default): main axis is horizontal, cross axis vertical.
  • flex-direction: column: main axis is vertical, cross axis horizontal.

Every alignment property is defined in terms of those axes, so once you know the direction you know what each property does.

Property (on the container)What it controls
flex-directionrow, row-reverse, column, column-reverse
justify-contentDistribution along the main axis: flex-start, center, flex-end, space-between, space-around, space-evenly
align-itemsAlignment on the cross axis: stretch (default), flex-start, center, flex-end, baseline
flex-wrapnowrap (default) keeps items on one line; wrap lets them flow onto new lines
gapSpace between items, without margins on the outer edges

The quickest way to make the axes stick is to play with them. The box below is a real flex container, so the browser does the layout exactly as it would on your page. Switch flex-direction first and watch the axis arrows swap, then notice that justify-content always follows the main axis and align-items always follows the cross axis.

InteractiveFlexbox playgroundOpen in Labs
Loading interactive…

Try column with justify-content: center, then raise the item count with flex-wrap: wrap. Baseline lines up the text of items with different font sizes; stretch only shows when items have no fixed cross size.

Worked example: a navigation bar

html
<header class="site-header">
  <a class="logo" href="/">Ridgeway</a>
  <nav>
    <ul class="nav-links">
      <li><a href="/shoes">Shoes</a></li>
      <li><a href="/jackets">Jackets</a></li>
      <li><a href="/sale">Sale</a></li>
    </ul>
  </nav>
</header>
css
.site-header {
  display: flex;
  align-items: center;            /* vertically centre logo and nav */
  justify-content: space-between; /* logo left, nav right */
  padding: 1rem 1.5rem;
}

.nav-links {
  display: flex;                  /* a second, nested flex container */
  gap: 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

Notice that <ul> needs its own display: flex: the <li> elements are grandchildren of .site-header, so its flex layout does not reach them.

Sizing flex items: grow, shrink and basis

Each item has three sizing properties:

  • flex-basis – the item's starting size along the main axis before free space is shared out (auto means "use my width or content size").
  • flex-grow – how much of any leftover space the item takes, as a ratio. 0 means "don't grow".
  • flex-shrink – how much the item gives up when space runs out. 0 means "never shrink below the basis".

You almost always set them with the flex shorthand:

ShorthandExpands toBehaviour
flex: initial (the default)0 1 autoContent-sized, can shrink, will not grow
flex: 11 1 0%Share space equally, ignoring content size
flex: auto1 1 autoGrow and shrink, starting from content size
flex: none0 0 autoFixed at content size

A search form where the input fills the space and the button stays its natural size is one line of CSS:

css
.search { display: flex; gap: 0.5rem; }
.search input { flex: 1; }

Auto margins

In a flex container, a margin of auto absorbs all free space on that side. That gives you a neat way to push one item away from the rest:

css
.toolbar { display: flex; gap: 0.5rem; }
.toolbar .logout { margin-inline-start: auto; } /* pushed to the far end */

Flexbox pitfalls

Long content overflows instead of shrinking. Flex items have min-width: auto by default, meaning they refuse to shrink below their content's minimum size. A long URL or a wide code block will burst out of the container. The fix is to allow shrinking explicitly:

css
.message-body {
  flex: 1;
  min-width: 0;        /* let the item shrink below its content size */
  overflow-wrap: anywhere;
}

Using Flexbox for a grid of cards. With flex-wrap: wrap and flex: 1 1 200px, the last row's items stretch to fill the row, so a row of two cards is wider than the rows of three above it. Flexbox sizes each line independently. When you want columns that line up, use Grid.

Reordering with order or row-reverse. These change the visual order but not the DOM order, so keyboard focus and screen readers follow the original order. Change the HTML instead whenever the order carries meaning.

Grid: two dimensions

Grid defines tracks – columns and rows – and places items into the cells they form. You describe the structure on the container:

css
.products {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 1.5rem;
}

The fr unit means "a fraction of the free space". 1fr 1fr 1fr is three equal columns; 2fr 1fr gives the first column twice the share of the second. You can mix units: grid-template-columns: 16rem 1fr is a fixed sidebar plus a flexible main column.

Useful track functions

css
grid-template-columns: repeat(4, 1fr);           /* four equal columns */
grid-template-columns: minmax(12rem, 20rem) 1fr; /* sidebar between 12 and 20rem */
grid-template-columns: repeat(3, minmax(0, 1fr)); /* truly equal columns */

That last line matters. 1fr actually means minmax(auto, 1fr), so a column will not shrink below its content's minimum width – one long word can make "equal" columns unequal. minmax(0, 1fr) removes that floor.

The implicit grid

You rarely need to define rows. Items fill the defined columns left to right, and Grid creates new rows as needed. Control their size with grid-auto-rows:

css
.products {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: minmax(10rem, auto); /* at least 10rem tall, grow with content */
  gap: 1.5rem;
}

Placing items on lines

Grid lines are numbered from 1 at the start edge, and from −1 at the end edge. Items can span tracks:

css
.featured { grid-column: 1 / -1; }  /* from the first line to the last: full width */
.wide     { grid-column: span 2; }  /* take two columns wherever it lands */
.tall     { grid-row: span 2; }

Named areas

For page layouts, grid-template-areas lets you draw the layout in text:

css
.page {
  display: grid;
  grid-template-columns: 15rem 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  min-height: 100vh;
  gap: 1rem;
}

.page > header { grid-area: header; }
.page > aside  { grid-area: sidebar; }
.page > main   { grid-area: main; }
.page > footer { grid-area: footer; }

Each string is a row and each word a cell. Repeating a name makes that area span cells; areas must form rectangles. The middle row is 1fr, so the footer sits at the bottom of the viewport even on short pages.

Grids that adapt without media queries

Grid can work out how many columns fit:

css
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: 1rem;
}

Read it as: "make as many columns as fit, each at least 16rem, sharing leftover space equally." min(16rem, 100%) stops a column overflowing on screens narrower than 16rem. With auto-fill instead of auto-fit, empty tracks are kept when there are only a few items, so the items stay 16rem-ish instead of stretching across the whole row.

Alignment and centring

Grid uses the same alignment vocabulary, along two axes: inline (the direction text runs, usually horizontal) and block (usually vertical).

  • justify-items / justify-self – align items inside their cells along the inline axis.
  • align-items / align-self – along the block axis.
  • place-items: center – shorthand for both.

So the shortest way to centre anything in a box is:

css
.hero {
  display: grid;
  place-items: center;
  min-height: 60vh;
}

The Flexbox equivalent is display: flex; justify-content: center; align-items: center;.

Choosing between them

Use Flexbox when…Use Grid when…
Items sit in a single row or columnItems need to line up in rows and columns
Item sizes should come from their contentThe container should dictate the track sizes
Building components: toolbars, button groups, media objectsBuilding structure: page layouts, card galleries, forms with aligned labels

They combine naturally: a Grid page layout whose header is a flex container, whose cards are grid items containing flex rows. Choose per container, not per project.

Practice

Build a single-page layout in one HTML file:

  1. A page grid with named areas: header across the top, a 14rem sidebar and main content in the middle, footer across the bottom. The footer should stay at the bottom of the window on short pages.
  2. In the header, a logo on the left and three nav links on the right, vertically centred.
  3. In the main area, a gallery of six cards that shows as many columns as fit (each at least 15rem). The first card should span the full width.
  4. Give one card a very long unbroken word and make sure it does not break the layout.
Show solution
html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Layout practice</title>
  <style>
    *, *::before, *::after { box-sizing: border-box; }
    body { margin: 0; font-family: system-ui, sans-serif; }

    .page {
      display: grid;
      grid-template-columns: 14rem minmax(0, 1fr);
      grid-template-rows: auto 1fr auto;
      grid-template-areas:
        "header header"
        "sidebar main"
        "footer footer";
      min-height: 100vh;
    }
    .page > header { grid-area: header; }
    .page > aside  { grid-area: sidebar; background: #f1f5f9; padding: 1rem; }
    .page > main   { grid-area: main; padding: 1rem; }
    .page > footer { grid-area: footer; background: #0f172a; color: white; padding: 1rem; }

    .site-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 1rem;
      border-bottom: 1px solid #cbd5e1;
    }
    .nav-links { display: flex; gap: 1rem; list-style: none; margin: 0; padding: 0; }

    .cards {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
      gap: 1rem;
    }
    .card { border: 1px solid #cbd5e1; border-radius: 8px; padding: 1rem; overflow-wrap: anywhere; }
    .card:first-child { grid-column: 1 / -1; }
  </style>
</head>
<body>
  <div class="page">
    <header class="site-header">
      <a href="/">Logo</a>
      <nav>
        <ul class="nav-links">
          <li><a href="#">One</a></li>
          <li><a href="#">Two</a></li>
          <li><a href="#">Three</a></li>
        </ul>
      </nav>
    </header>
    <aside>Sidebar</aside>
    <main>
      <div class="cards">
        <article class="card">Featured card</article>
        <article class="card">Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspaces</article>
        <article class="card">Card 3</article>
        <article class="card">Card 4</article>
        <article class="card">Card 5</article>
        <article class="card">Card 6</article>
      </div>
    </main>
    <footer>Footer</footer>
  </div>
</body>
</html>

The minmax(0, 1fr) main column and overflow-wrap: anywhere on the cards together stop the long word forcing the layout wider than the window. The fixed 14rem sidebar will still crowd small phone screens – making the page adapt to narrow screens is the subject of the next lesson.

Key takeaways

  • display: flex or display: grid affects only the container's direct children.
  • Flexbox arranges items along one axis; justify-content works on the main axis and align-items on the cross axis.
  • flex: 1 shares space equally; add min-width: 0 when long content refuses to shrink.
  • Grid defines tracks with fr, repeat() and minmax(); place items with line numbers, span, or named areas.
  • repeat(auto-fit, minmax(min(X, 100%), 1fr)) creates a card grid that adapts to its width.
  • Use Flexbox for content-sized components and Grid for aligned two-dimensional structure – often both on the same page.

Check yourself

Quiz

0/4 correct · 4 left

  1. 1.A flex container has flex-direction: column. Which property centres its items horizontally?

  2. 2.In a flex row, a chat message containing a very long URL overflows its container even though it has flex: 1. What is the usual fix?

  3. 3.What does grid-column: 1 / -1 do to a grid item?

  4. 4.You need a product gallery where cards line up in neat columns and rows, and the number of columns depends on the available width. Which is the best fit?

Finished reading?

Mark it complete to fill in your roadmap.