Pseudo-Classes
📦 Chapter 8 — Flexbox
Before Flexbox, making elements sit neatly side by side — or perfectly centred, or evenly spaced — required hacks involving floats, inline-block, and tables. Flexbox arrived to solve exactly this problem. It is a one-dimensional layout system: you decide on a direction (row or column) and then Flexbox handles the distribution of space, alignment, and ordering of everything inside. It has become the most widely-used CSS layout tool, and understanding it will unlock the majority of real-world layouts.
1 — The Container and Its Items
Flexbox involves two roles:
- The flex container — the parent element you set
display: flexon. It controls how its children are arranged. - Flex items — the direct children of the flex container. They respond to the container's rules and can also control their own size and alignment.
Properties are split between the two roles. Container properties control the overall layout strategy; item properties control individual item behaviour within that strategy.
flex-direction
justify-content
align-items
align-content
flex-wrap
gap
flex-shrink
flex-basis
flex (shorthand)
align-self
order
.container {
display: flex; /* block-level flex container */
}
/* All direct children immediately become flex items.
They no longer need display: inline-block — flex handles sizing. */
.inline-container {
display: inline-flex; /* flex container that sits inline with text */
}
2 — The Two Axes
Every Flexbox layout has two axes:
- Main axis — the direction items are placed in (controlled by
flex-direction). By default this is horizontal (left to right). - Cross axis — perpendicular to the main axis. If items run left-to-right, the cross axis runs top-to-bottom.
justify-content = main axis. align-items = cross axis. When flex-direction changes, both axes swap — so justify-content in a column container distributes items vertically.
3 — flex-direction
Sets which direction items flow and which end they start from. The default is row.
.container {
display: flex;
flex-direction: row; /* default — left to right */
flex-direction: row-reverse; /* right to left */
flex-direction: column; /* top to bottom */
flex-direction: column-reverse; /* bottom to top */
}
row-reverse and column-reverse affect visual order only — they do not change the tab/reading order in the DOM, which can cause accessibility problems if overused.4 — justify-content (main axis)
Controls how items are distributed along the main axis when there is extra space in the container.
.container {
display: flex;
justify-content: flex-start; /* pack to the start (default) */
justify-content: flex-end; /* pack to the end */
justify-content: center; /* pack to the centre */
justify-content: space-between; /* gaps between; none at edges */
justify-content: space-around; /* half-gap at edges */
justify-content: space-evenly; /* equal gaps everywhere */
}
space-between is the most used — logo left, nav right. space-evenly produces equal space everywhere including the outer edges, which makes it ideal for icon grids or tab bars.
5 — align-items (cross axis)
Controls how items are aligned along the cross axis — vertically when items flow in a row, horizontally when they flow in a column.
.container {
display: flex;
align-items: stretch; /* default — items fill the cross axis */
align-items: flex-start; /* align to the start of the cross axis */
align-items: flex-end; /* align to the end */
align-items: center; /* vertically centre (in a row container) */
align-items: baseline; /* align by text baseline */
}
justify-content: center; align-items: center; on a flex container is the quickest and most reliable way to perfectly centre a single child element — horizontally and vertically simultaneously.6 — flex-wrap and gap
By default, flex items try to fit on a single line — if there are too many to fit, they shrink. flex-wrap: wrap lets them spill onto additional lines instead.
.container {
display: flex;
flex-wrap: nowrap; /* default — single line, items shrink */
flex-wrap: wrap; /* wrap onto new lines */
flex-wrap: wrap-reverse; /* wrap, but new lines appear above */
/* gap — space between flex items (not at the edges) */
gap: 16px; /* same gap in both directions */
gap: 12px 20px; /* row-gap column-gap */
row-gap: 12px; /* vertical gap between wrapped rows */
column-gap: 20px; /* horizontal gap between items */
}
gap only adds space between items — never at the edges of the container. This is much cleaner than using margin on every child, which would create double-gaps between items and extra space at the edges.7 — flex-grow, flex-shrink, flex-basis
These three item properties control how a flex item sizes itself relative to the available space:
- flex-basis — the ideal starting size before free space is distributed. Can be a length (
200px), a percentage, orauto(use the item's content size). - flex-grow — how much of the remaining free space this item claims, relative to its siblings. A value of
1means "take an equal share". A value of2means "take twice as much as a sibling with1". - flex-shrink — how much this item shrinks when there is not enough space. Default is
1(all items shrink equally). Set to0to prevent shrinking.
/* Long form */
.item {
flex-grow: 1; /* claim free space proportionally */
flex-shrink: 1; /* shrink proportionally when space is tight */
flex-basis: auto; /* start from the item's natural content size */
}
/* The flex shorthand — use this in practice */
.item { flex: 1; } /* flex: 1 1 0 — grow, shrink, start from 0 */
.item { flex: auto; } /* flex: 1 1 auto — grow, shrink, use content size */
.item { flex: none; } /* flex: 0 0 auto — rigid, does not grow or shrink */
.item { flex: 0 0 200px; } /* fixed 200px — no growing, no shrinking */
/* Equal-width columns — the most common flex pattern */
.three-col .col { flex: 1; } /* each column takes one equal share */
/* Sidebar + main content layout */
.sidebar { flex: 0 0 240px; } /* fixed 240px sidebar */
.main { flex: 1; } /* main takes all remaining space */
flex shorthand, flex: 1 sets flex-basis to 0 (not auto), meaning items all start from zero and divide the total space equally regardless of content. This is usually what you want for equal-width columns.8 — align-self and order
align-self
Overrides the container's align-items value for a single item. All the same values apply: flex-start, center, flex-end, stretch, baseline.
.container { display: flex; align-items: flex-start; }
/* Override for individual items */
.special { align-self: center; } /* vertically centred */
.pinned { align-self: flex-end; } /* pinned to the bottom */
.full { align-self: stretch; } /* stretches to fill row height */
order
Changes the visual display order of flex items without changing the HTML. Items are sorted by their order value (lowest first). The default is 0.
order for decorative rearrangements (e.g. reversing a card layout on mobile) — never to fix a logical reading sequence that should be corrected in the HTML itself.
9 — Practical Patterns
Navigation bar — logo left, links right
.nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
height: 56px;
}
.nav-links {
display: flex;
gap: 24px;
}
Centred hero section
Welcome to the course
Learn CSS from the ground up with real examples.
.hero {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
min-height: 60vh;
gap: 16px;
}
Equal-width card row with a "Read more" pinned to the bottom
.card-row {
display: flex;
gap: 16px;
}
.card {
flex: 1; /* equal widths */
display: flex; /* nested flex container */
flex-direction: column;
padding: 16px;
}
.card-description {
flex: 1; /* grows to push the link to the bottom */
}
.card-link {
margin-top: auto; /* alternative: auto margin pushes it to the bottom */
}
margin-top: auto on a flex item is a powerful trick — it absorbs all the remaining free space on the main axis, pushing the element to the far end of the container. Perfect for pinning buttons to the bottom of cards of varying height.10 — Quick Reference
Container
| Property | Values | What it does |
|---|---|---|
display | flex / inline-flex | Activates Flexbox on the container |
flex-direction | row · row-reverse · column · column-reverse | Sets the main axis direction |
justify-content | flex-start · flex-end · center · space-between · space-around · space-evenly | Distributes items along the main axis |
align-items | stretch · flex-start · flex-end · center · baseline | Aligns items along the cross axis |
flex-wrap | nowrap · wrap · wrap-reverse | Controls whether items wrap onto new lines |
gap | length / two lengths | Space between items (row-gap column-gap) |
align-content | Same as align-items + space-between etc. | Aligns wrapped rows along the cross axis (requires wrap) |
Items
| Property | Values | What it does |
|---|---|---|
flex | flex: grow shrink basis | Shorthand for all three sizing properties |
flex-grow | number (default 0) | How much free space this item claims |
flex-shrink | number (default 1) | How much this item shrinks when space is tight |
flex-basis | length / auto | The item's ideal starting size |
align-self | Same as align-items values + auto | Overrides align-items for this single item |
order | integer (default 0) | Visual order — lower values appear first |
✏️ Exercises
Work through these in order — each one builds intuition for a different aspect of Flexbox. Use DevTools to inspect the computed flex values and watch the layout update as you change properties live.
<div class="toolbar"> containing: a logo/brand name on the left, a group of three icon-buttons in the centre, and a "Log in" button on the right. The three icon-buttons should have equal spacing between them. The whole toolbar should be 60px tall and vertically centre all its children. Use only Flexbox — no positioning, no floats.justify-content: space-between on the toolbar to push the three groups to the edges, then nest a second flex container inside for the centre icon group with gap. Alternatively, use flex: 1 on the left and right sections and justify-content: center on the icon group.<div class="toolbar">
<div class="tb-brand">⬡ MySite</div>
<div class="tb-icons">
<button>🔍</button>
<button>🔔</button>
<button>⚙️</button>
</div>
<div class="tb-actions">
<button class="login-btn">Log in</button>
</div>
</div>
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
padding: 0 20px;
background: #12141e;
border-radius: 6px;
}
.tb-brand {
font-weight: 700;
color: #4f8ef7;
font-family: system-ui, sans-serif;
}
.tb-icons {
display: flex;
gap: 8px;
}
.tb-icons button {
background: none;
border: 1px solid #2a2d3a;
border-radius:6px;
padding: 6px 10px;
cursor: pointer;
font-size: 1rem;
}
.login-btn {
background: #4f8ef7;
color: #fff;
border: none;
border-radius:5px;
padding: 8px 16px;
font-weight: 600;
font-family: system-ui, sans-serif;
cursor: pointer;
}
display: flex. The sidebar uses flex: 0 0 250px and the main content uses flex: 1. The inner content area is itself a column-direction flex container so the footer can be pushed to the bottom with margin-top: auto.<div class="layout">
<aside class="sidebar">
<p>Sidebar navigation</p>
</aside>
<main class="main-content">
<h2>Page Title</h2>
<p>Main content goes here.</p>
<div class="footer-bar">
<button>Cancel</button>
<button class="primary">Save</button>
</div>
</main>
</div>
.layout {
display: flex;
gap: 20px;
min-height: 400px;
}
.sidebar {
flex: 0 0 250px;
background: #1a1d27;
border: 1px solid #2a2d3a;
border-radius:6px;
padding: 16px;
}
.main-content {
flex: 1;
display: flex;
flex-direction: column;
background: #1a1d27;
border: 1px solid #2a2d3a;
border-radius: 6px;
padding: 20px;
}
.footer-bar {
display: flex;
justify-content: space-between;
margin-top: auto; /* pin to the bottom */
padding-top: 16px;
border-top: 1px solid #2a2d3a;
}
<span class="tag"> elements with different text lengths (single words, two-word phrases, etc.). Use display: flex; flex-wrap: wrap; gap: 8px; on the container. The tags should naturally flow to new rows when they run out of horizontal space. Give each tag a pill shape with padding and a rounded border. Hover state should change the background colour.flex: 1 — leave them at their natural size so they wrap organically. Try resizing your browser window to watch them reflow..tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag {
padding: 5px 14px;
border-radius: 999px;
border: 1px solid #2a2d3a;
background: #1a1d27;
color: #a0a4b8;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.tag:hover {
background: #1e2a40;
color: #7ab8ff;
border-color:#4f8ef7;
}
align-self: stretch and a larger padding, or use align-items: flex-start on the container and an explicit larger min-height on the featured card. Each card should have a plan name, price, a short feature list, and a CTA button pinned to the bottom.align-items: stretch (the default) on the container so all cards fill the row height, then give the featured card more padding to make it look larger. Apply margin: -12px 0 to the featured card to make it protrude above and below the others.<div class="pricing">
<div class="plan">
<h3>Basic</h3>
<div class="price">£9/mo</div>
<ul><li>5 projects</li><li>1 GB storage</li></ul>
<button>Get started</button>
</div>
<div class="plan featured">
<h3>Pro</h3>
<div class="price">£29/mo</div>
<ul><li>Unlimited projects</li><li>50 GB storage</li><li>Priority support</li></ul>
<button class="primary">Get started</button>
</div>
<div class="plan">
<h3>Enterprise</h3>
<div class="price">£99/mo</div>
<ul><li>Unlimited everything</li><li>SLA guarantee</li></ul>
<button>Contact sales</button>
</div>
</div>
.pricing {
display: flex;
gap: 16px;
}
.plan {
flex: 1;
display: flex;
flex-direction: column;
background: #1a1d27;
border: 1px solid #2a2d3a;
border-radius: 8px;
padding: 20px;
font-family: system-ui, sans-serif;
}
.plan.featured {
background: #1a2240;
border-color:#4f8ef7;
padding: 28px 20px;
margin: -12px 0; /* protrudes above and below */
}
.plan button {
margin-top: auto; /* pin to bottom */
padding: 10px;
border: 1px solid #2a2d3a;
border-radius:5px;
cursor: pointer;
font-weight: 600;
background: #1e2235;
color: #e2e4ec;
}
.plan button.primary {
background: #4f8ef7;
color: #fff;
border-color: #4f8ef7;
}