Flexbox
A deep dive into display: flex — main and cross axes, justify-content, align-items, and flex-grow/shrink/basis.
What flexbox solves
Before flexbox, centering something vertically or building an evenly-spaced row of items required hacks — floats, negative margins, display: table. Flexbox (display: flex) is a one-dimensional layout model purpose-built for exactly that: arranging items in a row or column, distributing space between them, and aligning them, all with a small, purpose-built set of properties.
.container {
display: flex;
}
The moment you set display: flex on a parent, its direct children become flex items and immediately line up in a row — no floats, no clearfixes.
The two axes
Flexbox thinks in terms of a main axis and a cross axis, and which is which depends on flex-direction:
.container {
display: flex;
flex-direction: row; /* main axis: horizontal (default) */
/* flex-direction: column; */ /* main axis: vertical */
}
flex-direction: row(default) — main axis is horizontal, cross axis is vertical.flex-direction: column— main axis is vertical, cross axis is horizontal.
Every alignment property below operates relative to whichever axis is currently "main" vs "cross" — this is the single most important mental model in flexbox.
justify-content — aligning along the main axis
.container {
display: flex;
justify-content: flex-start; /* default — items packed at the start */
/* justify-content: center; */ /* items centered */
/* justify-content: flex-end; */ /* items packed at the end */
/* justify-content: space-between; */ /* first/last flush to edges, gap distributed between */
/* justify-content: space-around; */ /* equal space around each item */
/* justify-content: space-evenly; */ /* fully equal space everywhere, including edges */
}
align-items — aligning along the cross axis
.container {
display: flex;
align-items: stretch; /* default — items stretch to fill the container's cross-axis size */
/* align-items: flex-start; */ /* items align to the top (in a row) */
/* align-items: center; */ /* items vertically centered */
/* align-items: flex-end; */ /* items align to the bottom */
}
Combining justify-content: center and align-items: center is the famous "perfectly centered <div>" one-liner flexbox is known for:
.center-everything {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
flex-grow, flex-shrink, flex-basis
These three control how individual flex items size themselves relative to available space, usually written together as the flex shorthand:
.item {
flex: 1 1 200px;
/* │ │ └─ flex-basis: starting size before growing/shrinking */
/* │ └─── flex-shrink: how eagerly this item shrinks if there's not enough room */
/* └───── flex-grow: how eagerly this item grows to fill extra space */
}
flex-grow— a unitless proportion of any leftover space this item should claim relative to its siblings.flex-grow: 2grows twice as fast as a sibling withflex-grow: 1.flex-shrink— same idea, but for shrinking when there isn't enough room.flex-shrink: 0prevents an item from shrinking at all.flex-basis— the item's initial size before growing/shrinking is applied — effectively awidth(orheight, in column direction) that flex-grow/shrink then adjust from.
Common shorthand values:
.equal-width-item { flex: 1; } /* flex: 1 1 0% — items share space equally */
.fixed-width-item { flex: 0 0 200px; } /* never grows, never shrinks, always 200px */
.grow-only-item { flex: 1 0 auto; } /* grows to fill space, won't shrink below content size */
Practical example: a responsive navbar
<nav class="navbar">
<div class="navbar-brand">NOA Labs</div>
<ul class="navbar-links">
<li><a href="/tutorials">Tutorials</a></li>
<li><a href="/roadmaps">Roadmaps</a></li>
<li><a href="/pricing">Pricing</a></li>
</ul>
<button class="navbar-cta">Sign up</button>
</nav>
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
background: #111827;
}
.navbar-links {
display: flex;
gap: 1.5rem;
list-style: none;
margin: 0;
padding: 0;
}
.navbar-links a {
color: #e5e7eb;
text-decoration: none;
}
.navbar-cta {
flex-shrink: 0; /* never let the button get squeezed by the links */
}
justify-content: space-between pushes the brand to the left edge and the CTA button to the right edge, with the nav links naturally filling the space between — no manual margin math required.
Practical example: an equal-height card row
<div class="card-row">
<div class="card">Short content</div>
<div class="card">A bit more content here to show height matching</div>
<div class="card">Content</div>
</div>
.card-row {
display: flex;
gap: 1rem;
}
.card {
flex: 1; /* every card claims equal width */
padding: 1.5rem;
background: #f9fafb;
border-radius: 8px;
/* align-items: stretch (the flex default) already gives every card equal height */
}
Because align-items defaults to stretch, every card automatically matches the height of its tallest sibling — a problem that used to require JavaScript or table layout to solve reliably.
gap
gap puts space between flex items without adding margin to the items themselves (so no "extra margin on the last item" cleanup needed):
.container {
display: flex;
gap: 1rem; /* same gap on both axes */
/* row-gap: 1rem; column-gap: 2rem; */ /* independent control */
}
Common mistakes
- Forgetting that
justify-contentandalign-itemsswap meaning betweenflex-direction: rowandcolumn—align-itemsalways targets the cross axis, not literally "vertical." - Using margins to fake gaps between flex items instead of the
gapproperty, requiring extra:last-childoverrides to avoid unwanted trailing margin. - Expecting
flex: 1on every item to always produce perfectly equal widths — it only produces equal widths when items have equal (or no) content-basedflex-basisand similar minimum content sizes; long unbreakable content can still force an item wider. - Not setting
flex-shrink: 0on an item that should never shrink (like an icon or button next to flexible text) — by default, everything can shrink.