Core Frontend
Flexbox: Everything You Need to Lay Things Out in One Dimension
A complete, visual guide to CSS Flexbox — the two axes, flex-direction, wrapping, justify-content, align-items, gap, grow/shrink/basis, the flex shorthand, auto margins, the min-width:0 gotcha, RTL behaviour, and hands-on exercises with solutions.
Once you can measure a box, the next question is how to arrange a row of them. Flexbox is the tool built for exactly that: take a set of items, line them up along one axis, and let them share the leftover space instead of you computing widths by hand. Centering, navbars, toolbars, card rows, sidebars — almost every "put these next to each other and make it behave" problem is a flexbox problem.
Flexbox answers one question: given a line of items and some free space, how is that space distributed along the line, and how are the items aligned across it?
One Dimension, Two Axes
Flexbox lays items out along a single dimension at a time — a row or a column. That dimension is the main axis; the perpendicular one is the cross axis. Nearly every flex property is just "do something along the main axis" or "do something along the cross axis", so getting these two straight is most of the battle.
flex-direction: row, the main axis runs across (left to right) and the cross axis runs down. justify-content works along the main axis; align-items works along the cross axis.The most important consequence: the axes swap when you switch direction. In a column, the main axis runs top-to-bottom, so justify-content now controls vertical placement and align-items controls horizontal. Memorise the roles (justify = main, align = cross), not "horizontal/vertical".
Turning It On: display: flex
You opt in on the container, not the items:
.container {
display: flex; /* a block-level flex container */
/* display: inline-flex; flows inline, like inline-block */
}
The moment an element becomes a flex container, three things happen to its direct children (now "flex items"):
- They line up along the main axis instead of stacking.
- They sit on a single line by default (no wrapping).
- They stretch to fill the cross axis (equal-height columns, for free).
Only direct children become flex items — flex does not reach grandchildren. And float, clear, and vertical-align stop having any effect on flex items.
flex-direction: Choosing the Main Axis
.container {
flex-direction: row; /* default — main axis is inline (writing) direction */
flex-direction: row-reverse; /* same line, items reversed */
flex-direction: column; /* main axis runs down */
flex-direction: column-reverse; /* main axis runs up */
}
row to column rotates the main axis — and with it, which property (justify vs align) controls each direction.Prefer row/column and let the page's writing direction handle mirroring. Reach for -reverse only when you truly want a visual reversal that should not flip in RTL — it changes paint order but not DOM or tab order, which can hurt accessibility if overused.
Wrapping: flex-wrap and flex-flow
By default a flex line never breaks — items shrink to fit and, if they can't shrink any further, they overflow. flex-wrap lets them spill onto new lines instead:
.container {
flex-wrap: nowrap; /* default — one line, may overflow */
flex-wrap: wrap; /* break onto new lines as needed */
flex-wrap: wrap-reverse; /* wrap, but stack lines the other way */
/* shorthand for direction + wrap: */
flex-flow: row wrap;
}
flex-wrap: wrap is the foundation of responsive card grids: give each card a flex-basis (or min-width) and let the row break naturally as the screen narrows — no media queries required for the basic reflow.
justify-content: Distribution Along the Main Axis
This is how leftover main-axis space is shared out:
space-between pins the first and last items to the edges; space-around gives each item equal space on both sides (so edges get a half-gap); space-evenly makes every gap — including the edges — identical..container {
justify-content: flex-start; /* default */
justify-content: center;
justify-content: flex-end;
justify-content: space-between;
justify-content: space-around;
justify-content: space-evenly;
}
align-items: Alignment Across the Cross Axis
Where justify-content distributes along the line, align-items positions items across it — the single-line cross-axis control:
stretch is the default — items with no fixed cross-axis size grow to fill the container, which is why flex rows give equal-height columns automatically. The others pin items to the start, centre, or end of the cross axis..container {
align-items: stretch; /* default */
align-items: flex-start;
align-items: center;
align-items: flex-end;
align-items: baseline; /* align items' text baselines */
}
baseline is the quiet hero for toolbars and form rows: it lines up text along its baseline even when items have different font sizes or padding.
Centering, finally solved
The two-line answer to "how do I center a thing both ways" is just main-axis + cross-axis alignment:
.center {
display: flex;
justify-content: center; /* main axis */
align-items: center; /* cross axis */
}
align-content: Aligning Multiple Lines
There's a third alignment property that only does something when content has wrapped onto multiple lines. align-items aligns items within their line; align-content aligns the lines themselves within the container's cross axis (flex-start, center, space-between, stretch, …). On a single-line flex container it has no effect — a common source of "why is this property doing nothing?".
gap: Spacing Without the Margin Headaches
.container {
gap: 16px; /* both row and column gaps */
gap: 12px 24px; /* row-gap | column-gap */
column-gap: 24px;
row-gap: 12px;
}
gap adds space between items only — never on the outer edges — and unlike margins it never collapses and never needs :not(:last-child) tricks. For spacing flex (and grid) children, gap is the modern default; reach for margins only when you need space on one specific side.
Sizing Items: flex-grow, flex-shrink, flex-basis
This is the heart of flexbox — how items resize to consume or surrender space. Three properties live on the items:
flex-basis— the item's initial main size before free space is handed out.auto(default) means "use mywidth/heightor content size"; a length like200pxor0sets it explicitly.flex-grow— how greedily an item absorbs leftover space.0(default) = don't grow. Higher numbers take proportionally bigger shares.flex-shrink— how readily an item gives up space when the line overflows.1(default) = shrink as needed.0= refuse to shrink.
flex: 1 and the others at flex: 0 0 auto, the flexible item absorbs everything left over after the fixed ones are placed.The flex shorthand (use this, not the longhands)
You'll almost always set all three at once with the flex shorthand. The values you actually use, day to day:
| Shorthand | Expands to | Meaning |
|---|---|---|
flex: 1 | 1 1 0% | Grow & shrink from a zero basis → equal flexible columns |
flex: auto | 1 1 auto | Grow & shrink from content size → flexible but content-aware |
flex: none | 0 0 auto | Fixed size, never grow or shrink |
flex: 0 1 auto | (the default) | Don't grow, shrink if needed — the initial behaviour |
flex: 0 0 240px | 0 0 240px | A rigid 240px column (great for sidebars) |
The big gotcha lives here: flex: 1 uses a zero basis, so three items at flex: 1 end up truly equal regardless of content. flex: auto uses content as the basis, so a longer item starts wider and stays a bit wider. Pick flex: 1 when you want exactly-equal columns and flex: auto when content should influence the split.
Per-Item Overrides: align-self and order
.item {
align-self: center; /* override the container's align-items for THIS item */
order: -1; /* move this item earlier; default 0, lower paints first */
}
align-self is perfect for nudging a single item (say, an avatar that should center while siblings stretch). order reshuffles the visual sequence without touching the HTML — handy, but remember it does not change reading or tab order, so don't use it to fix source-order problems that matter for accessibility.
Auto Margins Eat Free Space
A flex-only trick worth memorising: an auto margin on a flex item soaks up all the free space on that side. margin-inline-start: auto on the last item shoves it (and everything after) to the far end — the cleanest way to build a "logo on one side, actions on the other" bar:
.navbar { display: flex; gap: 16px; align-items: center; }
.navbar .spacer-target { margin-inline-start: auto; } /* pushed to the end */
Auto margins also center: margin: auto on a flex item centers it on both axes (yes, including vertically — the thing plain block layout could never do cleanly).
The min-width: 0 Gotcha
By default, a flex item won't shrink below its content's minimum size (min-width: auto). Usually invisible — until an item holds a long unbroken string, a <pre>, or a child with overflow, and it refuses to shrink, blowing out the layout or forcing a scrollbar. The fix is to let it shrink past content:
.flex-child {
min-width: 0; /* allow shrinking below content size (row) */
/* min-height: 0; the column-direction equivalent */
}
/* classic: truncate text inside a flex child */
.label {
flex: 1;
min-width: 0; /* without this, ellipsis never kicks in */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
If text-overflow ellipsis "isn't working" inside flexbox, this is almost always why.
Flexbox and Direction (RTL)
Flexbox is direction-aware by design, and that's a feature. With flex-direction: row on an RTL page, the main axis runs right-to-left automatically: justify-content: flex-start means the right edge, items flow rightward, and a navbar mirrors correctly with zero extra code. That's exactly why you should prefer logical, direction-relative tools:
- Keep
flex-direction: row/columnand letdir="rtl"flip the main axis for you — avoid hardcodedrow-reversefor "Arabic mode". - Use
margin-inline-start/margin-inline-end(notleft/right) for the auto-margin push trick, so it flips with direction. gapis direction-agnostic and needs nothing special.
For a site that ships in both LTR and RTL, leaning on flex's native direction handling deletes an entire class of mirroring bugs.
When Flexbox, When Grid?
Quick rule: Flexbox is one-dimensional (content flows along a single line, wrapping as a side effect), Grid is two-dimensional (you place items into rows and columns at once). Reach for flexbox for toolbars, button rows, navbars, centering, and "let these share a line"; reach for grid when you're defining an actual row-and-column structure. They compose happily — a grid cell can be a flex container and vice versa.
Common Mistakes
- Setting flex properties on the items when they belong on the container (
justify-content,align-items,flex-wrapare container properties). - Confusing the axes after switching to
column—justify-contentis now vertical,align-itemshorizontal. - Expecting
align-contentto do something on a single-line (non-wrapping) container. - Forgetting
min-width: 0, then wondering why a long string or ellipsis breaks the layout. - Using
flex: autoand being surprised columns aren't perfectly equal — useflex: 1for equal columns. - Reaching for
marginbetween items whengapis cleaner and collapse-free. - Using
row-reverse/physical margins for RTL instead of lettingdirand logical properties flip things.
Exercises
Work through these in a scratch HTML file (or any playground). Try each before opening the solution. Every starting point uses this minimal markup unless noted:
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
Exercise 1 — Perfect centering
Center a single child both horizontally and vertically inside a 300×200 box.
<div class="frame">
<div class="badge">Centered</div>
</div>
Show solution
.frame {
width: 300px;
height: 200px;
display: flex;
justify-content: center; /* main axis: horizontal */
align-items: center; /* cross axis: vertical */
}
Two alignment lines — no transforms, no magic numbers.
Exercise 2 — Navbar with pushed-right actions
Build a bar with a logo on the start side and two links pushed to the end, vertically centered. Must mirror correctly in RTL.
<nav class="navbar">
<span class="logo">Logo</span>
<a href="#">Docs</a>
<a href="#">Login</a>
</nav>
Show solution
.navbar {
display: flex;
align-items: center;
gap: 16px;
}
.navbar a:first-of-type {
margin-inline-start: auto; /* absorbs free space, pushes links to the end */
}
The auto margin on the first link soaks up all leftover space, shoving it and everything after to the far end. Because it's inline-start (logical), it flips automatically in RTL.
Exercise 3 — Responsive card row that wraps
Lay out cards in a row that wrap to new lines as the screen narrows, each card at least 220px wide and growing to share leftover space. No media queries.
<div class="cards">
<article>Card A</article>
<article>Card B</article>
<article>Card C</article>
<article>Card D</article>
</div>
Show solution
.cards {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.cards article {
flex: 1 1 220px; /* grow & shrink, ideal basis 220px */
}
Each card wants to be 220px; extra space is shared via flex-grow: 1; when the row can't fit another 220px card, flex-wrap drops it to the next line. Fully fluid with zero breakpoints.
Exercise 4 — Media object with truncating text
A fixed 48px avatar on the start side, then a title that fills the rest and truncates with an ellipsis when too long.
<div class="media">
<img class="avatar" src="..." alt="" />
<p class="title">A very long title that should be cut off with an ellipsis…</p>
</div>
Show solution
.media {
display: flex;
align-items: center;
gap: 12px;
}
.avatar {
flex: 0 0 48px; /* rigid: never grow, never shrink */
width: 48px;
height: 48px;
}
.title {
flex: 1;
min-width: 0; /* THE fix — let the text shrink below its content size */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
Without min-width: 0, the title refuses to shrink below its text width and the ellipsis never appears — the single most common flexbox surprise.
Exercise 5 — Sticky footer
Make the footer sit at the bottom of the viewport when content is short, but get pushed down naturally when content is tall.
<body class="layout">
<header>Header</header>
<main>Content</main>
<footer>Footer</footer>
</body>
Show solution
.layout {
min-height: 100vh; /* or 100dvh */
display: flex;
flex-direction: column; /* main axis runs down */
}
.layout main {
flex: 1; /* main grows to absorb all spare vertical space */
}
In a column flex container, flex: 1 on main makes it eat the leftover height, pinning the footer to the bottom when content is short and letting it flow down when content is tall.
Exercise 6 — Sidebar + fluid content
A fixed 240px sidebar next to a content area that fills the rest. Stack them on narrow screens.
<div class="shell">
<aside>Sidebar</aside>
<section>Main content</section>
</div>
Show solution
.shell {
display: flex;
flex-wrap: wrap;
gap: 24px;
}
.shell aside {
flex: 0 0 240px; /* rigid 240px column */
}
.shell section {
flex: 1 1 320px; /* fills remaining space; its 320px basis forces a wrap-to-stack when cramped */
}
On wide screens the rigid sidebar and the flexible content share the row; once the content's 320px basis no longer fits beside the 240px sidebar, flex-wrap stacks them. A grid-like layout with one declaration each and no breakpoints.
The Mental Model to Keep
A flex container lines its items along a main axis, with the cross axis running perpendicular. justify-content distributes along the main axis; align-items aligns across the cross axis; switching to column swaps which is which. Items resize through flex: grow shrink basis — reach for flex: 1 (equal), flex: auto (content-aware), or flex: none (rigid). Use gap for spacing, min-width: 0 to tame stubborn children, auto margins to push things apart, and lean on the native direction handling so RTL just works. Master those, and one-dimensional layout stops being a fight.