Flexbox in Practice — Real Layouts
Building real, common UI patterns with Flexbox — a responsive navbar, an equal-height card grid, the centering reflex, a sticky footer layout, and the gap property.
gap — Spacing Between Flex Items, Without Margin Hacks
Before gap was supported on flex containers, spacing items apart required margin tricks on individual items, almost always combined with an awkward :not(:last-child) selector (or similar) to avoid an unwanted extra margin on the final item. gap replaces all of that with a single declaration on the container itself.
.toolbar {
display: flex;
gap: 16px; /* 16px of space between EVERY adjacent item — never on the outer edges */
}
/* Different horizontal vs vertical spacing (matters once flex-wrap creates multiple rows) */
.gallery {
display: flex;
flex-wrap: wrap;
gap: 24px 16px; /* row-gap: 24px, column-gap: 16px */
}The critical detail that makes gap genuinely better than margin-based spacing: it only ever inserts space between items, never on the outer edges of the container — there is no equivalent of the old "last item has an unwanted trailing margin" problem to work around, because gap was designed around exactly that pain point.
gap works identically on Grid containers too (covered in the CSS Grid modules later in this track) — it is not a Flexbox-only property, though it originated in Grid and was later added to Flexbox. Browser support for gap on flex containers specifically is universal in every browser this track targets, so there is no longer a real reason to reach for margin-based spacing hacks in new Flexbox code.justify-content + align-items — The Single-Container Centering Reflex
Centering a single element — both horizontally and vertically, inside its parent — was genuinely painful before Flexbox: margin math, absolute positioning combined with negative margins or transforms, or table-cell display hacks were all common workarounds. Flexbox reduces the entire problem to two declarations on the parent.
.centered-wrapper {
display: flex;
justify-content: center; /* centers along the main axis (horizontal, by default) */
align-items: center; /* centers along the cross axis (vertical, by default) */
}
<!-- Works identically whether the child is text, a button, an image,
or an entire card — and works even when the child's size is
completely unknown ahead of time, which the old margin-based
"margin: 0 auto" trick could never do for vertical centering. -->
<div class="centered-wrapper" style="height: 100vh;">
<div class="login-card">...</div>
</div>This exact three-declaration pattern is so common in real interfaces — modals, loading spinners, empty states, login screens, "no results found" messages — that it is worth committing to memory as a single reflex rather than deriving it from first principles each time: display: flex, justify-content: center, align-items: center.
height: 100vh for full-viewport centering) or inherited from its own layout context — before align-items: center has any vertical room to work with.Building a Responsive Navbar
A standard navbar — logo on the left, nav links in the middle or right, all vertically centered on one row — is one of the most universal real-world Flexbox layouts, and combines several properties from the previous module in one practical structure.
<nav class="navbar">
<a class="navbar-logo" href="/">Chaduvuko</a>
<ul class="navbar-links">
<li><a href="/learn">Learn</a></li>
<li><a href="/pricing">Pricing</a></li>
<li><a href="/about">About</a></li>
</ul>
<div class="navbar-actions">
<a class="btn-ghost" href="/login">Log in</a>
<a class="btn-primary" href="/signup">Sign up</a>
</div>
</nav>
.navbar {
display: flex;
align-items: center; /* vertically centers logo, links, and actions on the row */
justify-content: space-between; /* pushes logo, links, and actions apart across the row */
padding: 16px 32px;
gap: 24px;
}
.navbar-links {
display: flex; /* the <ul> becomes its OWN flex container */
align-items: center;
gap: 32px;
list-style: none; /* remove default bullet styling */
}
.navbar-actions {
display: flex; /* a third, independent flex container */
align-items: center;
gap: 12px;
}A detail worth calling out explicitly: this navbar uses three separate flex containers, nested — the outer .navbar, plus .navbar-links and .navbar-actions each independently. Flex containers do not interfere with each other across nesting boundaries; .navbar-links's gap and align-items only affect its own direct children (the li elements), completely independent of the outer navbar's own flex settings.
Making the navbar collapse responsively
@media (max-width: 768px) {
.navbar-links {
display: none; /* hidden below 768px — a real mobile navbar would
swap this for a hamburger-triggered menu instead,
covered fully in the Responsive Design module */
}
}Building an Equal-Height Card Grid
A row of cards where each card's content (title, description, button) is a different length is a genuinely common real layout — and getting every card to be the same height, with each card's "action" button pinned to the same bottom position regardless of how much text sits above it, is a two-layer Flexbox problem: the row of cards, and the internal structure of each individual card.
.card-row {
display: flex;
gap: 24px;
align-items: stretch; /* this is ALREADY the default, written here for clarity —
every card stretches to match the tallest sibling's height */
}
.card {
flex: 1; /* equal width per Module 23's flex: 1 vs flex: 1 1 auto distinction */
padding: 24px;
border-radius: 12px;
background: white;
}align-items: stretch — the default cross-axis behavior — is doing all of the height-matching work here, with zero extra CSS. Every flex item in a row container automatically stretches to the tallest sibling's height unless the item has an explicit height or align-self overriding it. This single default behavior is the entire reason equal-height card layouts became trivial once Flexbox arrived — the pre-Flexbox equivalent required either JavaScript measuring the tallest card and setting heights manually, or a display: table-cell hack.
Pinning each card's button to the bottom, regardless of content length
.card {
flex: 1;
display: flex; /* the card is now ALSO its own flex container */
flex-direction: column; /* stacking title, description, button vertically */
padding: 24px;
}
.card-description {
flex-grow: 1; /* absorbs all leftover vertical space inside the card,
pushing whatever comes after it all the way down */
}
.card-button {
/* naturally ends up flush against the bottom of the card, because
.card-description above it has already claimed every pixel of
leftover space */
}This is the exact same "one flex-grow: 1 spacer absorbs the leftover space" idea from the toolbar pattern in the previous module — applied here to a column-direction container instead of a row, so the leftover space being absorbed is vertical instead of horizontal. Recognizing this as the same underlying pattern, rather than a separate trick to memorize, is exactly the kind of transfer that makes Flexbox click.
The Sticky Footer Layout — Pinning a Footer to the Bottom of Short Pages
"Sticky footer" here means something specific and often misunderstood: a footer that sits at the very bottom of the viewport on pages with too little content to naturally reach that far — but scrolls normally and appears right after the content on pages long enough to fill the screen on their own. It is not position: sticky (that CSS property solves a different problem, covered in the Display & Positioning module) — it is a full-page Flexbox structure.
<body>
<header class="site-header">...</header>
<main class="site-main">...</main>
<footer class="site-footer">...</footer>
</body>
html, body {
height: 100%; /* required — body must be able to inherit full viewport height */
margin: 0;
}
body {
display: flex;
flex-direction: column; /* header, main, footer stacked vertically */
min-height: 100vh; /* the whole page is AT LEAST one full viewport tall */
}
.site-main {
flex-grow: 1; /* absorbs all leftover vertical space — pushes the footer
down to the bottom edge on short pages, without pinning
the footer in place on TALL pages, which is exactly
the behavior position: fixed could never give you */
}The key detail: min-height: 100vh on the body, not a fixed height: 100vh. Using a fixed height would clip or misbehave on pages whose content genuinely needs to be taller than one viewport — min-height guarantees the layout is at least one full screen tall (so the footer never floats mid-page on short content) while still allowing the page to grow taller than the viewport when content demands it, with .site-main's flex-grow: 1 absorbing exactly however much extra space exists in either case.
flex-grow: 1 element absorbing all the leftover space in a flex-direction: column container, pushing whatever comes after it to the very bottom. Once you have internalized this one idea, you have effectively solved three separate- looking real-world layout problems (toolbar spacer, card button pinning, sticky footer) with the exact same tool.Combining Everything — A Complete Product Card Component
Real UI components rarely use Flexbox in isolation from other layout tools — this product card combines a Flexbox row for the badge/price header, align-self (a per-item override of the container's align-items) for a single mis-aligned element, and the column-stacking + flex-grow button-pinning pattern from Part 04, all in one realistic component.
<div class="product-card">
<img class="product-image" src="/sneaker.jpg" alt="Running shoe" />
<div class="product-body">
<div class="product-header">
<span class="product-badge">New</span>
<span class="product-price">$89</span>
</div>
<h3 class="product-title">Trail Runner Pro</h3>
<p class="product-description">
Lightweight trail running shoe with reinforced toe cap and grip sole.
</p>
<button class="product-cta">Add to Cart</button>
</div>
</div>
.product-card {
display: flex;
flex-direction: column;
width: 280px;
border-radius: 12px;
overflow: hidden;
background: white;
}
.product-body {
display: flex;
flex-direction: column;
flex: 1; /* the body fills whatever height the card ends up being,
inside a row of equal-height cards elsewhere on the page */
padding: 20px;
gap: 8px;
}
.product-header {
display: flex;
justify-content: space-between; /* badge left, price right */
align-items: center;
}
.product-description {
flex-grow: 1; /* same pattern as Part 04 — pushes the button to the bottom */
color: #64748b;
}
.product-badge {
align-self: flex-start; /* overrides the row's own align-items just for
this one item, in case the price text wraps
to two lines and grows taller than the badge */
}align-self is worth knowing specifically because it is the escape hatch for the "every item in this row should align one way, except this one" situation — it accepts the exact same values as align-items (flex-start, flex-end, center, stretch, baseline), but applies to a single flex item rather than the whole container.A Two-Line Footer Bug at a Portland Nonprofit's Donation Page
A volunteer web developer builds a short "Thank You" confirmation page shown after a donation completes — a few lines of text, a receipt number, and a "Return to homepage" link. Design QA immediately flags it: on desktop, the footer floats awkwardly in the vertical middle of the page, with a large empty gray gap below it, instead of sitting at the bottom of the screen the way every other page on the site does.
body {
display: flex;
flex-direction: column;
/* min-height is MISSING entirely */
}
.site-main {
flex-grow: 1;
}
.site-footer {
padding: 24px;
background: #f1f5f9;
}What the developer finds
Without a min-height declared on body, the flex container is only as tall as its content actually needs — for this short thank-you page, that is far less than a full viewport. .site-main's flex-grow: 1 is absorbing 100% of the leftover space correctly, but there is almost no leftover space to absorb in the first place, because the container itself never grew to fill the screen. The footer ends up sitting directly beneath the short amount of content, with the browser's own background showing through as a large blank gap below everything.
body {
display: flex;
flex-direction: column;
min-height: 100vh; /* the fix — guarantees the flex container is AT LEAST
one full viewport tall, giving .site-main real
leftover space to absorb on short pages */
margin: 0;
}The footer now sits flush against the bottom of the viewport on short pages like this one, while longer pages elsewhere on the site (which already had enough content to exceed one viewport height) continue to render exactly as they did before — min-height never constrains a page that is already taller than the viewport. This is a genuinely common first-attempt mistake with the sticky footer pattern: every other piece is correct, and the layout still breaks because one property, easy to forget, was never set.
Four Misconceptions About Flexbox in Real Layouts
5 Interview Questions — With Complete Answers
Real-Layout Flexbox Mistakes Beginners Make Constantly
Rendering Bugs You Will Hit — And Exactly Why
🎯 Key Takeaways
- ✓gap adds space only BETWEEN flex items, never on the container's outer edges — it replaces old margin-based spacing hacks and the extra selectors they required.
- ✓The centering reflex — display: flex; justify-content: center; align-items: center; — centers a child within whatever space the parent already occupies. It does not create height; the parent needs a real height or min-height first.
- ✓A responsive navbar is commonly built from several independent, nested flex containers — logo/links/actions as one outer row, plus separate inner flex containers for the links and actions groups.
- ✓Equal-height cards need zero extra CSS beyond display: flex — align-items: stretch is already the default cross-axis behavior for flex items in a row.
- ✓Pinning a button to the bottom of a card (or a footer to the bottom of a page) both use the same technique: a flex-direction: column container with flex-grow: 1 on the element that should absorb all leftover vertical space.
- ✓A sticky footer layout needs min-height: 100vh on the body — without it, flex-grow: 1 on the main content has no leftover space to absorb on short pages, and the footer floats mid-screen.
- ✓align-self overrides align-items for a single flex item — the right tool when one item in a row needs different cross-axis alignment than the rest.
What comes next
Module 25 introduces CSS Grid — true two-dimensional layout, grid-template-columns/rows, grid areas, the fr unit, and the mental model that makes Grid click once Flexbox's one- dimensional limits start to show.
Module 25 → CSS Grid — The Complete GuideDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.