Lesson 4

Flexbox

Home › Courses › CSS › Lesson 4
🔵 Lesson 4 · 25 minutes

Flexbox.
The layout system that changed everything.

Before flexbox, centering a div was a Stack Overflow question with 3 million views. After flexbox, it's two lines. This is the most important layout system in modern CSS — every navigation bar, card layout, button group, and centered element uses it. Learn flexbox properly and 80% of "how do I arrange these" problems disappear. Every example has a live preview: click and see it work.

~25 minutes Beginner → Intermediate CSS3 · flexbox
CSS path ← Lesson 3 Basics justify-content align-items gap Direction Wrap flex-grow Patterns Challenge

Add display: flex to a parent to make it a flex container. Its direct children become flex items, laid out in a row by default. Use justify-content to distribute along the main axis (row: horizontal). Use align-items to align on the cross axis (row: vertical). Use gap for spacing between items. Use flex-wrap: wrap to allow wrapping. Use flex: 1 on children to make them grow equally. Those six properties cover 95% of flexbox usage.

Before this lesson: complete Lesson 3. You need to understand sizing, spacing, and the box model before laying out multiple items.

🎯 The basics — display: flex

One line turns a parent into a flex container. Three children line up in a row.

CSS
/* The container — becomes a flex container */
.container {
display: flex;
gap: 12px;
padding: 16px;
background: rgba(21, 114, 182, 0.1);
border-radius: 8px;
}

/* The children — become flex items automatically */
.item {
background: #1572B6;
color: white;
padding: 20px;
border-radius: 6px;
text-align: center;
font-family: system-ui, sans-serif;
font-weight: 600;
}

display: flex

Turns the element into a flex container. Its direct children become flex items. Only direct children — grandchildren aren't affected unless you make them flex containers too.

Main axis vs cross axis

The main axis is the direction items flow — horizontal in a row (default). The cross axis is perpendicular — vertical in a row. justify-content follows main, align-items follows cross.

Items grow by default

Flex items stretch to fill available space unless you constrain them. This is why three items in a flex container look equally wide — each got flex: 1 implicitly? No — they got equal distribution because none had a fixed basis. Set width or flex-basis to change this.

Only works on direct children

display: flex on a parent affects only its direct children. If you have a nested div inside a flex item, that nested div is NOT a flex item — it's just a normal block, unless you make it a flex container too.

The mental model: main axis and cross axis. Every flexbox property answers one question: "which axis?" justify-content works on the main axis — the direction items flow (left-right in row, top-bottom in column). align-items works on the cross axis — perpendicular to the main. If you remember "justify = main, align = cross," flexbox stops being confusing. This is the one insight that takes beginners from "I guess this works" to "I know why this works."

↔️ justify-content — main axis alignment

Six values. All about horizontal distribution (in a row layout).

CSS — all 6 justify-content values
/* flex-start — items packed to the start (default) */
.justify-start { display: flex; justify-content: flex-start; }

/* flex-end — items packed to the end */
.justify-end { display: flex; justify-content: flex-end; }

/* center — items packed in the middle */
.justify-center { display: flex; justify-content: center; }

/* space-between — first item at start, last at end, equal gaps between */
.justify-between { display: flex; justify-content: space-between; }

/* space-around — equal space around each item */
.justify-around { display: flex; justify-content: space-around; }

/* space-evenly — equal space between all items AND edges */
.justify-evenly { display: flex; justify-content: space-evenly; }

flex-start / flex-end

Items packed to the start or end of the container. Default is flex-start. Use flex-end for things like "logout button on the right side of a nav."

center

Items packed in the middle. The most-used value after space-between. Use for horizontally centered buttons, logos, and nav items.

space-between

First item at start, last item at end, equal gaps between. The classic "logo on left, nav on right" layout. Probably the second-most-used value after flex-start.

space-around / space-evenly

space-around puts equal space around each item (so half the space at edges). space-evenly puts equal space between items AND at edges. The difference is subtle — visually test which you prefer.

space-between is the workhorse. Every nav bar, header, card header, and footer using flexbox starts with justify-content: space-between. It's how you put a logo on the left and links on the right with one line. .navbar { display: flex; justify-content: space-between; }. Learn this pattern — you'll use it hundreds of times.

↕️ align-items — cross axis alignment

Vertical alignment in a row layout. The property that finally made vertical centering easy.

CSS — align-items values
/* stretch — items fill the cross axis (default) */
.align-stretch { display: flex; align-items: stretch; }

/* flex-start — items at the top (row layout) */
.align-start { display: flex; align-items: flex-start; }

/* flex-end — items at the bottom */
.align-end { display: flex; align-items: flex-end; }

/* center — items in the middle (VERTICAL CENTERING) */
.align-center { display: flex; align-items: center; }

/* baseline — items aligned by their text baseline */
.align-baseline { display: flex; align-items: baseline; }

stretch (default)

Items fill the container's height. This is why nav items look equal height without any extra CSS. Set a container height and children stretch to fill it.

flex-start / flex-end

Items at top or bottom. Use flex-start for content that should anchor to the top of a card, flex-end for footer-like content at the bottom.

center

Vertical centering. Combined with justify-content: center, centers both axes. This is the modern "center a div" solution — two lines, works every time.

baseline

Items align by their text baseline. Rare but useful when items have different font sizes and you want the text to line up at the bottom of the letters instead of the box bottoms.

The perfect centering recipe. display: flex; justify-content: center; align-items: center; — three lines that center any element (or multiple elements) both horizontally and vertically. Works on any container with a defined height. This is the answer to "how do I center a div" — a question that got 3 million Stack Overflow views before flexbox existed.

📏 gap — spacing between items

One property that replaced margin hacks. Cleaner than margin-right on every child.

CSS
/* gap — consistent spacing between items, no space outside /
.gap-demo {
display: flex;
gap: 16px; / one property, applies between all items */
padding: 16px;
background: rgba(21, 114, 182, 0.1);
border-radius: 8px;
}

/* Different row and column gaps /
.gap-demo-wrap {
display: flex;
flex-wrap: wrap;
row-gap: 16px; / space between rows /
column-gap: 8px; / space between items in a row /
/ or: gap: 16px 8px; (row-gap column-gap) */
}

/* OLD WAY — margin with last-child exception */
.old-way {
display: flex;
}
.old-way > * {
margin-right: 16px;
}
.old-way > :last-child {
margin-right: 0; / required — otherwise extra space at end */
}

/* NEW WAY — one property */
.new-way {
display: flex;
gap: 16px;
}

gap

Applies consistent spacing between flex items. Only between them — no space before the first or after the last. One property replaces N margin declarations.

row-gap / column-gap

For wrapped flex containers, control rows and columns separately. row-gap: 16px for vertical space, column-gap: 8px for horizontal. Or gap: 16px 8px.

Browser support

Flexbox gap is supported in every modern browser since 2020. No fallback needed. Use it — it's the clean way to space flex children.

gap vs margin

gap is between items only. margin applies to every item including creating space around the outer edges. For spacing between flex children, gap is always the better choice.

Stop using margin-right on flex children. The pattern .item { margin-right: 16px; } .item:last-child { margin-right: 0; } was how everyone spaced items before gap. It required the last-child override, otherwise there was extra space at the end that broke centering and alignment. gap: 16px is one property, no exceptions, works perfectly. If you see the old margin pattern in code, replace it — the gap version is objectively better.

🔄 flex-direction — rows and columns

Switch between horizontal and vertical layouts. Also swaps the axis roles.

CSS
/* row — horizontal (default). justify = horizontal, align = vertical */
.dir-row {
display: flex;
flex-direction: row;
gap: 12px;
}

/* column — vertical. justify = vertical, align = horizontal */
.dir-column {
display: flex;
flex-direction: column;
gap: 12px;
}

/* row-reverse — horizontal, right to left */
.dir-row-reverse {
display: flex;
flex-direction: row-reverse;
gap: 12px;
}

/* column-reverse — vertical, bottom to top */
.dir-column-reverse {
display: flex;
flex-direction: column-reverse;
gap: 12px;
}

/* Card pattern — column direction with content + footer /
.card {
display: flex;
flex-direction: column;
justify-content: space-between; / space between content and footer */
height: 200px;
}

row (default)

Left to right. Main axis horizontal, cross axis vertical. justify-content = horizontal, align-items = vertical. The default for nav bars, buttons, cards.

column

Top to bottom. Main axis vertical, cross axis horizontal. justify-content = vertical, align-items = horizontal. The axis roles swap.

row-reverse / column-reverse

Same as row/column but reversed order. Rarely used — usually you'd reorder the HTML instead. Useful for right-aligned nav or reversed visual order on different screen sizes.

The axis swap

In column direction, justify-content distributes vertically (top-to-bottom) and align-items aligns horizontally. This is the single most confusing thing for beginners — remember: justify follows main, align follows cross.

The axis-swap trap. If you set flex-direction: column and expect justify-content: center to center horizontally, you'll be confused — it centers vertically. The mental model: justify-content works on the main axis (the direction of flex-direction), align-items works on the perpendicular axis. When you change direction, the roles of these properties flip. This is what makes flexbox feel unpredictable at first — but once internalized, it's clean.

📦 flex-wrap — allow wrapping

By default, flex items stay on one line. Enable wrapping for responsive layouts.

CSS
/* nowrap (default) — items squeezed into one line */
.nowrap {
display: flex;
flex-wrap: nowrap;
gap: 8px;
}

/* wrap — items wrap to next line when they run out of space */
.wrap {
display: flex;
flex-wrap: wrap;
gap: 8px;
}

/* wrap-reverse — wraps upward instead of downward */
.wrap-reverse {
display: flex;
flex-wrap: wrap-reverse;
gap: 8px;
}

/* The card-grid pattern — the classic responsive grid /
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.card-grid > .card {
flex: 1 1 250px; / grow to fill, but never smaller than 250px before wrapping /
/ This means: try to fit as many 250px+ cards per row as possible,
then grow them equally to fill the row */
}

/* The tag-cloud pattern — small chips that wrap */
.tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag {
padding: 6px 12px;
background: rgba(21, 114, 182, 0.2);
border-radius: 999px;
font-size: 13px;
}

nowrap (default)

All items on one line. If they don't fit, they shrink (or overflow if flex-shrink: 0). Use for navs and toolbars that should never wrap.

wrap

Items wrap to the next line when they run out of space. Essential for responsive layouts, card grids, and tag clouds. Usually what you want on mobile.

wrap + align-content

When you have multiple rows, align-content controls vertical spacing between rows. align-content: flex-start | center | space-between | .... Without wrapping, align-content has no effect.

flex-basis for wrapping

flex-basis: 250px on children means "minimum 250px before wrapping." Combined with flex: 1 1 250px, cards try to fit at 250px+ and grow to fill the row. The classic responsive-grid pattern.

The responsive card grid — without media queries. display: flex; flex-wrap: wrap; gap: 16px; on the container plus flex: 1 1 250px; on each card gives you a grid that auto-fits cards based on viewport width. One row on mobile (250px fits), two on tablet, three on desktop. No media queries needed. This pattern is used by Tailwind, Bootstrap, and every modern site. It's the flexbox equivalent of "grid."

⚖️ flex-grow / shrink / basis — sizing children

How children respond when there's too much or too little space.

CSS
/* flex: grow shrink basis — the shorthand */

/* Equal columns — all children grow equally /
.equal-children > * {
flex: 1; / shorthand for flex: 1 1 0% */
}

/* Different proportions — 2:1 ratio */
.two-to-one > *:nth-child(1) { flex: 2; }
.two-to-one > *:nth-child(2) { flex: 1; }

/* Fixed width — doesn't grow or shrink /
.fixed {
flex: 0 0 200px; / don't grow, don't shrink, exactly 200px */
}

/* Grow to fill but never shrink below content size */
.grow-only {
flex: 1 0 auto;
}

/* Responsive grid — grow, shrink, minimum 250px before wrapping */
.responsive {
flex: 1 1 250px;
}

/* The most common: sidebar with fixed width + main content /
.layout {
display: flex;
gap: 16px;
}
.layout .sidebar {
flex: 0 0 250px; / fixed 250px sidebar /
}
.layout .main {
flex: 1; / take all remaining space */
}

flex: 1

The most-used flex value. Means "grow to fill available space, shrink if needed, start from zero." All children with flex: 1 share space equally. Use for equal columns.

flex: 2 vs flex: 1

Proportional widths. flex: 2 gets twice the space of flex: 1. Use for asymmetrical layouts — a 2:1 column split, a sidebar that's narrower than main.

flex: 0 0 200px

Don't grow, don't shrink, exactly 200px. Use for sidebars, icons, avatars — things with a fixed size. If you want a fixed-width item, don't use flex: 1 on it.

flex: 1 1 250px

The responsive-grid pattern. "Grow, shrink, but start at 250px minimum before wrapping." Combined with flex-wrap: wrap, this creates grids that adapt to viewport width automatically.

The classic mistake: flex: 1 on everything. If you put flex: 1 on every child, they all become equal — even if one has an image and one has a short text. This is often what you want for navs and cards, but not for sidebars (which should be fixed width). Use flex: 1 where you want equal widths, and flex: 0 0 auto or flex: 0 0 250px where you don't.

🎯 Real-world patterns

Five layouts that cover most of what you'll build. Each is a preview.

CSS — 5 patterns
/* 1. NAV BAR — logo left, links right */
.nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
background: #1e293b;
}

/* 2. CARD with content + footer */
.card {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 200px;
padding: 20px;
background: #1e293b;
}

/* 3. HORIZONTALLY CENTERED HERO */
.hero {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
text-align: center;
}

/* 4. CARD GRID — auto-fit, wraps responsively */
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.card-grid > * {
flex: 1 1 250px;
}

/* 5. SIDEBAR LAYOUT — fixed sidebar + flexible main */
.layout {
display: flex;
gap: 24px;
}
.layout .sidebar { flex: 0 0 200px; }
.layout .main { flex: 1; }

These five patterns cover 80% of layouts. Every nav bar, every card, every centered hero, every grid of cards, every sidebar layout — all use one of these five. Learn them and you can build almost anything without referencing docs. They're not just examples — they're the standard patterns every professional uses. Copy them into your next project and adapt.

🏆 Challenge

Three tasks. Each exercises a different flexbox pattern.

Task 1 — Perfect centering

Build a container that's 400px tall with a centered card inside. The card should be:

1. Max 300px wide
2. White background (or dark surface)
3. 24px padding
4. Border radius 12px
5. Contains a title and a paragraph
6. Centered both horizontally and vertically in the container

Use only display: flex; justify-content: center; align-items: center; on the container. Test by resizing the browser — the card should stay centered.

Task 2 — Responsive card grid

Build a grid of 6 cards that:

1. Shows 1 card per row on mobile (<600px)
2. Shows 2 cards per row on tablet (600–1000px)
3. Shows 3 cards per row on desktop (>1000px)
4. Has consistent spacing between cards
5. Uses flexbox (not grid)

Hint: display: flex; flex-wrap: wrap; gap: 16px; on the container + flex: 1 1 300px; on the cards. No media queries needed — the cards wrap automatically based on available space.

Task 3 — Full navigation bar

Build a complete nav bar with:

1. Logo on the left
2. Three links in the center
3. A "Sign in" button on the right
4. All items vertically centered
5. Consistent spacing between items
6. On mobile (under 600px), stack everything vertically

Use flexbox with justify-content: space-between and align-items: center. For the mobile stacking, use @media (max-width: 600px) { .nav { flex-direction: column; } }. This is the exact pattern every real nav bar uses.

⚠️ Common mistakes

The flexbox bugs that catch every beginner.

1. Confusing main axis with cross axis

justify-content controls the MAIN axis (direction of flex-direction). align-items controls the CROSS axis. If justify-content: center isn't centering vertically, you're in row direction — use align-items: center for that.

2. Forgetting flex-wrap on responsive grids

Without flex-wrap: wrap, cards squeeze into one row and become unreadable on mobile. Fix: add flex-wrap: wrap to any container with many items.

3. Using margin-right instead of gap

The old pattern requires .item:last-child { margin-right: 0 } — otherwise there's extra space at the end. Fix: use gap: 16px. One property, no exceptions.

4. flex: 1 on everything

If all children get flex: 1, they're all equal width — even a 50px avatar and 500px text become equal. Fix: only use flex: 1 on items that should share space. Use flex: 0 0 auto for fixed-size items.

5. Forgetting align-items: center

Items default to stretch — they fill the container's height. If your logo and nav links look off-center vertically, add align-items: center to the nav container.

6. Fighting the flex items' default grow

Flex items grow by default — a card with less content still fills its row. If you want content-sized items (like tags or buttons), use flex: 0 0 auto or flex: 0 1 auto.

7. Setting width on flex children instead of flex-basis

width: 200px on a flex child can be overridden by flex-grow/shrink. Fix: use flex: 0 0 200px (fixed), or flex: 1 1 200px (grows to 200px+, then fills).

8. Expecting flex to work on grandchildren

display: flex on a parent affects only direct children. If you need grandchildren arranged in a row, make the child a flex container too. Nested flex is normal and expected.

➡️ What's next

Flexbox handles rows and columns. Next: full two-dimensional layouts.

❓ Frequently asked questions

What is CSS flexbox?

Flexbox is a CSS layout system for arranging items in a single dimension — either a row or a column. It replaced older techniques like float and inline-block because it handles alignment, spacing, and sizing automatically. One line, display: flex, enables a whole toolkit of properties. It's the most-used layout tool in modern CSS.

What is the difference between justify-content and align-items?

justify-content controls alignment along the MAIN axis (horizontal in row direction). align-items controls alignment along the CROSS axis (perpendicular). In a default row layout, justify-content = horizontal, align-items = vertical. If you change flex-direction to column, they swap roles.

What does flex-direction do?

flex-direction sets the main axis — the direction items are laid out. row (default): left to right. column: top to bottom. Changing direction swaps the roles of justify-content and align-items. row is the default and what you'll use 90% of the time.

What is the difference between gap and margin in flexbox?

gap applies consistent spacing between flex items — only between them, never outside. margin applies to each child including around outer edges. gap is cleaner: one property replaces margin-right on every child plus the last-child exception. Use gap for spacing between flex children.

How do I center an element with flexbox?

Two properties on the parent: display: flex; justify-content: center; align-items: center. justify centers horizontally, align centers vertically. Works regardless of child size. If the parent has a fixed height, the child centers vertically within it.

What is flex-wrap and why does it matter?

By default, flex items try to fit in one row. flex-wrap: wrap lets items wrap to the next line when they run out of space. Essential for responsive layouts, tags, and card grids. Add gap for spacing that applies both horizontally and vertically.

What is the difference between flex-grow, flex-shrink, and flex-basis?

flex-grow: how much a child grows to fill extra space (default 0). flex-shrink: how much a child shrinks when space is tight (default 1). flex-basis: initial size before growing/shrinking (default auto). The shorthand flex: 1 means flex-grow: 1, flex-shrink: 1, flex-basis: 0.

Should I use flexbox or grid?

Flexbox is one-dimensional (row OR column), grid is two-dimensional (rows AND columns). Use flexbox for component layouts: nav bars, button groups, card contents. Use grid for page layouts: dashboards, image galleries, article layouts. Modern sites often use both — grid for structure, flexbox for components.

🌐 External resources

← Back to CSS track
You're in the CSS track · Lesson 4