Skip to main content

📐 Lesson 13: Layout with Flexbox

So far, every element on your page stacks top-to-bottom like a pile of bricks. That's fine for simple pages — but what about a navigation bar with links side by side? A sidebar next to your main content? A row of cards? Flexbox is the CSS layout system that makes all of that effortless.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain the relationship between a flex container and its flex items
  • Use display: flex to lay out elements in a row or column
  • Control direction with flex-direction
  • Distribute space with justify-content
  • Align items with align-items
  • Wrap items onto multiple lines with flex-wrap
  • Control how items grow and shrink with flex-grow, flex-shrink, and flex-basis
  • Build a navigation bar and a card layout using Flexbox

Estimated Time: 60 minutes

Hands-on: You'll build a navigation bar and a responsive card row for your site.

📑 In This Lesson

The Problem Flexbox Solves

By default, block elements stack vertically. Each one takes the full width and sits below the previous one. Before Flexbox, developers used float and absolute positioning to place elements side by side — hacks that were fragile, hard to maintain, and terrible with spacing.

Flexbox was designed from scratch to solve one-dimensional layout — arranging items in a row or in a column with precise control over alignment, spacing, and sizing. It answers questions like:

  • "How do I put three cards side by side?"
  • "How do I center something both horizontally and vertically?"
  • "How do I push the last nav link to the right?"
  • "How do I make items wrap to the next line when they run out of space?"

One line of CSS — display: flex — unlocks all of this.

Flex Container & Flex Items

Flexbox has two roles: the container (parent) and its items (direct children). You apply display: flex to the parent, and all of its direct children automatically become flex items.

<div class="card-row">      <!-- flex container -->
    <div class="card">A</div> <!-- flex item -->
    <div class="card">B</div> <!-- flex item -->
    <div class="card">C</div> <!-- flex item -->
</div>
.card-row {
    display: flex;
}

That's it. The three cards now sit side by side in a row instead of stacking vertically. The container controls the layout; the items respond.

graph LR subgraph "Flex Container (display: flex)" A["Item A"] --- B["Item B"] --- C["Item C"] end style A fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style B fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style C fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b

💡 Key Insight

Flex properties are split between the container and the items. Properties like justify-content, align-items, flex-direction, flex-wrap, and gap go on the container. Properties like flex-grow, flex-shrink, flex-basis, and align-self go on individual items.

Flex Direction — Row or Column

flex-direction controls which way items flow. It defines the main axis — the primary direction of layout.

/* Default — items flow left to right */
.container { flex-direction: row; }

/* Items flow top to bottom (like default block behavior, but with flex powers) */
.container { flex-direction: column; }

/* Reversed versions */
.container { flex-direction: row-reverse; }    /* right to left */
.container { flex-direction: column-reverse; } /* bottom to top */
flex-direction Values
Value Main Axis Items Flow Common Use
row Horizontal → Left to right Navigation bars, card rows
column Vertical ↓ Top to bottom Sidebar stacks, mobile nav
row-reverse Horizontal ← Right to left RTL layouts, reversed order
column-reverse Vertical ↑ Bottom to top Chat messages (newest at bottom)

Main Axis vs. Cross Axis

Understanding Flexbox requires knowing two axes:

  • Main axis — the direction items flow (set by flex-direction). For row, it's horizontal. For column, it's vertical.
  • Cross axis — the perpendicular direction. For row, the cross axis is vertical. For column, it's horizontal.
graph LR subgraph "flex-direction: row" direction LR MA["Main Axis →
(horizontal)"] CA["Cross Axis ↓
(vertical)"] end style MA fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style CA fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b

Why does this matter? Because justify-content works along the main axis, and align-items works along the cross axis. Switch the direction, and they swap which dimension they control.

justify-content — Distributing Space Along the Main Axis

justify-content controls how items are positioned and how extra space is distributed along the main axis.

.container {
    display: flex;
    justify-content: flex-start; /* default */
}
justify-content Values
Value Behavior Visual (row)
flex-start Items packed to the start [A][B][C]           
flex-end Items packed to the end            [A][B][C]
center Items centered      [A][B][C]     
space-between First item at start, last at end, equal space between [A]     [B]     [C]
space-around Equal space around each item   [A]    [B]    [C]  
space-evenly Equal space between and at edges    [A]   [B]   [C]   

The three space-* values are the workhorses. space-between is probably the most used — it's perfect for navigation bars where you want the logo on the left and links on the right.

/* Navigation bar: logo left, links right */
.navbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 24px;
}

align-items — Cross-Axis Alignment

align-items controls alignment along the cross axis — the axis perpendicular to the main flow. For a row layout, that means vertical alignment.

.container {
    display: flex;
    align-items: stretch; /* default */
}
align-items Values
Value Behavior
stretch Items stretch to fill the container's cross axis (default)
flex-start Items align to the start of the cross axis (top for rows)
flex-end Items align to the end of the cross axis (bottom for rows)
center Items centered along the cross axis
baseline Items align by their text baselines

The Holy Grail: Perfect Centering

Before Flexbox, centering something both horizontally and vertically was surprisingly difficult. With Flexbox, it's two lines:

/* Perfect centering — both axes */
.centered {
    display: flex;
    justify-content: center;  /* center on main axis (horizontal) */
    align-items: center;      /* center on cross axis (vertical) */
    height: 100vh;            /* full viewport height so there's space to center in */
}

This is one of the most celebrated improvements Flexbox brought to CSS. Developers used to need complex workarounds for this; now it's trivial.

align-self — Override for One Item

If most items should align one way but a single item needs different alignment, use align-self on that item:

.container {
    display: flex;
    align-items: center;      /* most items centered */
}

.special-item {
    align-self: flex-end;     /* this one sticks to the bottom */
}

flex-wrap — Wrapping to New Lines

By default, flex items try to fit on one line — they'll shrink to avoid overflowing. If you want items to wrap onto new lines (like a grid of cards), use flex-wrap:

.card-grid {
    display: flex;
    flex-wrap: wrap;         /* allow items to wrap */
}

.card-grid .card {
    width: 300px;            /* each card wants to be 300px */
}

Now the cards sit side by side, and when the container isn't wide enough to fit the next card, it wraps to a new line — like words in a paragraph.

flex-wrap Values
Value Behavior
nowrap All items on one line (default) — they shrink if needed
wrap Items wrap to new lines when they run out of space
wrap-reverse Items wrap, but new lines appear above instead of below

💡 Shorthand: flex-flow

flex-flow combines flex-direction and flex-wrap into one property:

/* flex-flow: direction wrap; */
.container {
    flex-flow: row wrap;
}
/* Same as:
   flex-direction: row;
   flex-wrap: wrap;
*/

gap — Spacing Between Items

Before gap, developers added margin to flex items and had to deal with extra margin on the first or last item. The gap property creates space between items without affecting the outer edges.

.card-row {
    display: flex;
    gap: 20px;               /* 20px between each item */
}

/* You can also set row and column gaps separately */
.card-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 20px 16px;          /* 20px between rows, 16px between columns */
}
graph LR subgraph "gap: 20px" A["Card A"] -. "20px" .- B["Card B"] -. "20px" .- C["Card C"] end style A fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style B fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style C fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b

gap is cleaner than using margins because:

  • No extra space on the outer edges
  • No need for :first-child or :last-child overrides
  • It works with wrapping — gap applies between wrapped rows too

You can also use row-gap and column-gap individually if you need different spacing in each direction.

flex-grow, flex-shrink, flex-basis

These three properties go on flex items (not the container) and control how items share available space.

flex-basis — Starting Size

flex-basis sets the item's ideal starting size before any growing or shrinking. It works like width in a row layout (or height in a column layout).

.card {
    flex-basis: 300px;   /* start at 300px, then grow or shrink from there */
}

flex-grow — How Much Extra Space to Take

flex-grow controls how much of the leftover space an item absorbs. The default is 0 — items don't grow.

/* All items share extra space equally */
.item {
    flex-grow: 1;
}

/* The middle item gets twice as much extra space */
.sidebar  { flex-grow: 1; }  /* 1 share */
.main     { flex-grow: 2; }  /* 2 shares */
.sidebar2 { flex-grow: 1; }  /* 1 share */

In the example above, if there's 400px of extra space, .sidebar gets 100px, .main gets 200px, and .sidebar2 gets 100px.

flex-shrink — How Much to Give Up

flex-shrink is the opposite of flex-grow — it controls how much an item shrinks when there isn't enough space. The default is 1 — all items shrink equally.

/* This item refuses to shrink */
.logo {
    flex-shrink: 0;
}

/* This item shrinks twice as fast as others */
.description {
    flex-shrink: 2;
}

The flex Shorthand

The flex shorthand combines all three in one line:

/* flex: grow shrink basis; */
.item { flex: 0 1 auto; }    /* default: don't grow, shrink equally, auto sizing */
.item { flex: 1; }           /* grow equally, shrink equally, basis 0 */
.item { flex: 1 1 300px; }   /* grow equally, shrink equally, start at 300px */
.item { flex: 0 0 250px; }   /* don't grow, don't shrink, fixed at 250px */

The most common pattern you'll use: flex: 1 to make items share space equally, or flex: 0 0 250px for a fixed-size item.

graph TD A["Container: 900px"] --> B["flex: 1
Takes 1 share
= 300px"] A --> C["flex: 2
Takes 2 shares
= 600px"] style A fill:#f8fafc,stroke:#64748b,stroke-width:2px,color:#1e293b style B fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style C fill:#eff6ff,stroke:#6366f1,stroke-width:2px,color:#1e293b

Common Flexbox Patterns

Here are the patterns you'll use over and over again. Copy these as starting points for your own layouts.

Pattern 1: Navigation Bar

.navbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 24px;
    background-color: #1e293b;
    color: white;
}

.navbar .nav-links {
    display: flex;
    gap: 24px;
    list-style: none;
}
<nav class="navbar">
    <a href="/" class="logo">MySite</a>
    <ul class="nav-links">
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
        <li><a href="/contact">Contact</a></li>
    </ul>
</nav>

Pattern 2: Card Row (Responsive)

.card-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
}

.card-grid .card {
    flex: 1 1 280px;       /* grow, shrink, min 280px before wrapping */
    padding: 20px;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
}

The magic of flex: 1 1 280px: each card wants to be at least 280px. If there's room for three cards, you get three columns. If the screen is narrower, cards wrap — two per row, then one. No media queries needed.

Pattern 3: Sidebar Layout

.page-layout {
    display: flex;
    gap: 30px;
}

.sidebar {
    flex: 0 0 250px;       /* fixed 250px, won't grow or shrink */
}

.main-content {
    flex: 1;               /* takes all remaining space */
}
<div class="page-layout">
    <aside class="sidebar">
        <!-- navigation or filters -->
    </aside>
    <main class="main-content">
        <!-- primary content -->
    </main>
</div>

Pattern 4: Footer with Sections

.footer-content {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 30px;
    padding: 40px 24px;
}

.footer-section {
    flex: 1 1 200px;
}

Pattern 5: Centering Anything

.hero {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 60vh;
    text-align: center;
}

Hands-on Exercise

🏋️ Exercise: Flexbox Navigation & Card Row

Objective: Convert your navigation to a Flexbox layout and create a row of cards on your homepage.

Part A — Flex Navigation Bar:

  1. In your css/style.css, add display: flex to your header or <nav> element
  2. Use justify-content: space-between; so the site name sits left and the nav links sit right
  3. Use align-items: center; to vertically center everything
  4. Make the nav link list itself a flex container with gap: 20px
  5. Remove the default list bullets with list-style: none; and reset padding: 0; and margin: 0;

Part B — Card Row:

  1. On your homepage, create a section with 3 or more cards inside a container <div>
  2. Give the container display: flex; flex-wrap: wrap; gap: 20px;
  3. Give each card flex: 1 1 250px;
  4. Resize your browser window and watch the cards responsively reflow
💡 Hint

For the navigation, the key structure is: a <nav> with display: flex; justify-content: space-between; align-items: center; containing the site name and a <ul>. The <ul> also gets display: flex; gap: 20px;. Nested flex containers are perfectly normal — you'll use them all the time.

✅ Example Solution
/* ===========================
   Navigation Bar (Flex)
   =========================== */
.site-nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 24px;
    background-color: var(--color-primary, #1e293b);
    color: white;
}

.site-nav a {
    color: white;
    text-decoration: none;
}

.site-nav a:hover {
    text-decoration: underline;
}

.site-nav .logo {
    font-size: 1.25rem;
    font-weight: 700;
}

.site-nav .nav-links {
    display: flex;
    gap: 20px;
    list-style: none;
    margin: 0;
    padding: 0;
}

/* ===========================
   Card Row (Flex)
   =========================== */
.card-row {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
    margin-top: 24px;
}

.card-row .card {
    flex: 1 1 250px;
    background-color: white;
    padding: 20px 24px;
    border: 1px solid var(--color-border, #e2e8f0);
    border-radius: 8px;
}

.card-row .card h3 {
    margin-top: 0;
}

.card-row .card p {
    color: var(--color-text-light, #64748b);
}
<!-- Updated navigation -->
<nav class="site-nav">
    <a href="index.html" class="logo">My Website</a>
    <ul class="nav-links">
        <li><a href="index.html">Home</a></li>
        <li><a href="about.html">About</a></li>
        <li><a href="recipe.html">Recipes</a></li>
        <li><a href="contact.html">Contact</a></li>
    </ul>
</nav>

<!-- Card row on homepage -->
<section>
    <div class="container">
        <h2>What You'll Find Here</h2>
        <div class="card-row">
            <div class="card">
                <h3>🍳 Recipes</h3>
                <p>My favorite home-cooked meals, from quick weeknight dinners to weekend projects.</p>
                <a href="recipe.html">Browse recipes →</a>
            </div>
            <div class="card">
                <h3>📖 About Me</h3>
                <p>A little about who I am, what I do, and why I built this site.</p>
                <a href="about.html">Learn more →</a>
            </div>
            <div class="card">
                <h3>✉️ Get in Touch</h3>
                <p>Have a question or just want to say hello? Drop me a message.</p>
                <a href="contact.html">Contact me →</a>
            </div>
        </div>
    </div>
</section>

🎯 Quick Quiz

Question 1: What does display: flex do to an element's direct children?

Question 2: Which property distributes space between flex items along the main axis?

Question 3: How do you perfectly center an element both horizontally and vertically with Flexbox?

Question 4: What does flex-wrap: wrap do?

Question 5: What does flex: 1 1 300px mean?

Summary

🎉 Key Takeaways

  • Flexbox is a one-dimensional layout system — arrange items in a row or a column
  • Apply display: flex to the parent — its direct children become flex items
  • flex-direction sets the main axis: row (default) or column
  • justify-content distributes space along the main axis (e.g., space-between, center)
  • align-items aligns items on the cross axis (e.g., center, stretch)
  • flex-wrap: wrap lets items flow onto new lines — essential for responsive card layouts
  • gap creates space between items without affecting outer edges — cleaner than margins
  • flex: grow shrink basis controls how items share space — flex: 1 is the most common shorthand
  • Perfect centering: justify-content: center; align-items: center; — the Flexbox gift to CSS
  • Common patterns: navigation bars (space-between), card grids (wrap + flex-basis), sidebars (flex: 0 0 fixed + flex: 1)

📁 Your Project So Far

my-website/
├── css/
│   └── style.css      ← now with Flexbox nav and card layout
├── images/
│   └── sunset.jpg
├── index.html         ← flex nav bar + card row
├── about.html
├── recipe.html
└── contact.html

🚀 What's Next?

Flexbox handles one-dimensional layouts beautifully — rows or columns. But what about two-dimensional layouts where you need to control rows and columns at the same time? Think photo galleries, page-level layouts, and dashboard grids. In the next lesson, you'll learn CSS Grid — Flexbox's two-dimensional sibling that gives you full grid-based layout control.