# Flexbox & Grid

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

Frontend Developer roadmap · The web platform · 18 min read · https://levelcode.org/roadmaps/frontend/css-layout

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-direction` | `row`, `row-reverse`, `column`, `column-reverse` |
| `justify-content` | Distribution along the **main** axis: `flex-start`, `center`, `flex-end`, `space-between`, `space-around`, `space-evenly` |
| `align-items` | Alignment on the **cross** axis: `stretch` (default), `flex-start`, `center`, `flex-end`, `baseline` |
| `flex-wrap` | `nowrap` (default) keeps items on one line; `wrap` lets them flow onto new lines |
| `gap` | Space 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.

> **Interactive: Flexbox playground.** 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. Try it: https://levelcode.org/labs/flexbox-playground

### 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:

| Shorthand | Expands to | Behaviour |
| --- | --- | --- |
| `flex: initial` (the default) | `0 1 auto` | Content-sized, can shrink, will not grow |
| `flex: 1` | `1 1 0%` | Share space equally, ignoring content size |
| `flex: auto` | `1 1 auto` | Grow and shrink, starting from content size |
| `flex: none` | `0 0 auto` | Fixed 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; }
```

> [!NOTE]
> Cards in a row often need their inner parts – title, price, button – to line up with each other. Make each card span the rows it needs and give it `display: grid; grid-template-rows: subgrid;`, and the card's children will sit on the parent grid's row tracks.

### 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;`.

> [!TIP]
> Both Chrome and Firefox DevTools have Flexbox and Grid inspectors. Click the small "flex" or "grid" badge next to an element in the Elements panel to overlay line numbers, area names, gaps and free space directly on the page.

## Choosing between them

| Use Flexbox when… | Use Grid when… |
| --- | --- |
| Items sit in a single row or column | Items need to line up in rows **and** columns |
| Item sizes should come from their content | The container should dictate the track sizes |
| Building components: toolbars, button groups, media objects | Building 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.

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

## Quiz

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

   A. justify-content: center
   B. align-items: center
   C. text-align: center
   D. place-content: start

   **Answer: B.** With a column direction the main axis is vertical, so the horizontal axis is the cross axis, controlled by align-items. justify-content would centre them vertically.

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?

   A. Set flex-shrink: 0
   B. Change flex: 1 to flex: none
   C. Set justify-content: space-between on the container
   D. Set min-width: 0 on the item

   **Answer: D.** Flex items default to min-width: auto, so they will not shrink below their content's minimum size. min-width: 0 removes that floor. flex-shrink: 0 and flex: none prevent shrinking entirely.

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

   A. Makes it span from the first to the last column line, the full width
   B. Places it in the first column only
   C. Removes it from the grid
   D. Places it in the last column only

   **Answer: A.** Line -1 is the last explicit grid line, so 1 / -1 spans every column track. It is the standard way to make an item full width.

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?

   A. Flexbox with flex-wrap: wrap and flex: 1 1 200px
   B. Floats with fixed widths
   C. Grid with repeat(auto-fit, minmax(min(15rem, 100%), 1fr))
   D. Inline-block elements with text-align: justify

   **Answer: C.** Grid aligns items in both dimensions and auto-fit chooses the column count. Wrapped Flexbox sizes each line independently, so a short last row stretches wider than the rows above it.
