Flexbox, the 1D primitive
Flexbox, the CSS one-dimensional layout system, driven with Tailwind utilities to arrange elements in a row or column.
Open any SaaS app and the top bar is the same shape: a logo pinned left, a sign-in button pinned right, everything on one horizontal line. In 2026 that whole bar is three utilities, flex items-center justify-between. By the end of this lesson you’ll read that string and know exactly what each part does, then build four more layouts on the same handful of ideas.
In the last lesson you switched flex on as a display mode, which made an element’s children stop stacking and line up in a row. This lesson is the algorithm behind that switch: given a row or column of children with unpredictable widths, it decides who gets how much space, where the slack goes, and how everything aligns. Two ideas separate hearing about flexbox from shipping it. The first is that a flex container has two axes, which turns the choice between justify and items from a guess into a decision. The second is min-w-0, the utility that keeps a flex row from blowing out its container.
A flex container has two axes
Section titled “A flex container has two axes”Setting display: flex (Tailwind flex) on an element makes it a flex container . Every direct child automatically becomes a flex item : no per-child class, no opt-in.
A flex container lays its items out along two perpendicular directions. The main axis is the direction items flow, by default a horizontal row running left to right. The cross axis is perpendicular to it, vertical in a default row.
The habit to build, before you touch a single utility, is that every flex property targets one axis. Properties that distribute items along the flow work the main axis; properties that position items across the flow work the cross axis. So the first question is never “which utility do I want” but “which axis am I moving things on” — name the axis first, then pick the property. Most flexbox mistakes come from reaching for a property before naming its axis.
Naming the axis first matters because the axes aren’t fixed to “horizontal” and “vertical”; the main axis is wherever the layout flows. Flip a row into a column and the main axis rotates to vertical, dragging the cross axis with it. In the figure below, switch between Row and Column and watch the two arrows rotate together: the boxes barely move, but which arrow reads “main” changes.
The next four sections fill in which properties live on which axis. Because you’ll name the axis first, none of them will surprise you.
Direction and wrapping
Section titled “Direction and wrapping”The main axis can run in either direction. flex-row is the default and flows items left to right; flex-col rotates the axis to vertical so items stack top to bottom, as you saw in the column tab. You’ll pick row or column on essentially every flex container.
There are also flex-row-reverse and flex-col-reverse, worth knowing mainly so you know not to reach for them. They flip the visual order of the items but leave the source order untouched, and source order is what the keyboard tabs through. So a flex-row-reverse bar looks like it runs right to left, yet pressing Tab still walks the items left to right. To move a single item, reach for order-* on that item instead, which keeps the visual and source order closer together.
By default a flex container packs every item onto one line, shrinking them to fit rather than wrapping. flex-wrap lets items that don’t fit flow onto the next line. You’ll cover wrapping properly when you build wrapping grids later in this chapter; for now, just know it exists and that once items wrap, gap-y-* sets the gap between the rows.
Sizing items: grow, shrink, basis
Section titled “Sizing items: grow, shrink, basis”A nav bar is rarely the exact width of its contents, so when the container is wider or narrower than its items need, something has to absorb the difference: stretch to fill the slack, or give up space when there isn’t enough. Flexbox lets you decide, per item, how that negotiation goes.
Three properties on each item drive it. flex-grow is how eagerly an item expands to eat free space. flex-shrink is how willingly it gives space back when there isn’t enough. flex-basis is the size each item starts from before any growing or shrinking. The flex property is shorthand for all three. Every flex utility compiles down to these, but you’ll rarely set them by hand; instead you reach for three named shorthands that cover the cases that come up.
flex-1: grow and shrink from a basis of zero. Everyflex-1item starts from nothing and grows equally, so they share the available space in equal portions regardless of content. Reach for it when something should fill the leftover room: a search input next to a button, or the main column next to a sidebar.flex-auto: grow and shrink too, but from the item’s content size. Leftover space is handed out on top of what each item already needs, so a longer item stays wider than a shorter one.flex-none: don’t grow, don’t shrink. The item stays exactly its content (or declared) width. Reach for it when a size is part of the design and must not move.shrink-0: keep the default growing but switch off shrinking. This is the everyday reach for sidebars, fixed-width avatars, icons, and badges, anything that should hold its size while its neighbors flex around it.
That last one comes with a surprise: flex items shrink by default. Put an avatar with a fixed w-12 into a tight row and flexbox will happily squash it below 48px to make everything fit. Adding shrink-0 tells flexbox to take that width literally. When an avatar or icon looks subtly crushed in a row, a missing shrink-0 is the usual culprit.
The fastest way to feel the difference is to move the slack yourself. Drag the container width below and watch the three boxes react, then switch the middle box between flex-1, flex-auto, and flex-none to see the leftover space handed out differently. With flex-none, the middle box ignores the negotiation entirely while its neighbors keep flexing.
This side-by-side pins down the flex-1 versus flex-auto difference. Both rows hold the same three items; only the shorthand changes.
<div className="flex gap-3"> <div className="flex-1">A</div> <div className="flex-1">Medium label</div> <div className="flex-1">Longer label here</div></div>Equal columns. Every item starts from a zero basis, so all three end up the same width no matter how much text each holds.
<div className="flex gap-3"> <div className="flex-auto">A</div> <div className="flex-auto">Medium label</div> <div className="flex-auto">Longer label here</div></div>Content-sized, then stretched. Items start from their content width, so longer labels keep more room, and leftover space is shared on top of what each already needed.
Aligning items on both axes
Section titled “Aligning items on both axes”Alignment is the same move on each axis: pick the axis, then pick a value.
On the main axis, justify-* distributes items along the flow. The values are justify-start (the default, packed at the start), justify-end, justify-center, justify-between (first and last items flush to the edges, the rest spread evenly between), justify-around (equal space around each item), and justify-evenly (equal space between every item and the edges). justify-between is the nav-bar workhorse: it pushes the logo to one edge and the sign-in button to the other with no manual spacing.
On the cross axis, items-* positions items across the flow. The default, items-stretch, is why a flex child fills the full height of a row even when its content is short: unless you say otherwise, every item stretches to fill the cross axis. The other values pull items off that stretch: items-start, items-end, items-center, and items-baseline.
items-baseline is the one newcomers miss. Picture a row with a large heading and a small timestamp beside it. items-center centers the two boxes, and since the boxes differ in height, the text looks misaligned: the big and small text don’t share a line. items-baseline aligns the text baselines instead, so the words sit on a common line the way they would in a sentence. When you align text of different sizes in a row, baseline is usually what you want.
Two more utilities round out alignment, both recognition-level. self-* (align-self) overrides cross-axis alignment for a single item: set the row to items-center but pin one avatar to the top with self-start. content-* (align-content) positions the lines of a wrapped container relative to each other; you’ll rarely reach for it, since gap-y-* usually handles the spacing between wrapped rows.
The playground below drives a row of boxes with two dropdowns, one for justify-content and one for align-items. The container is deliberately tall so cross-axis movement is obvious. Change justify and the boxes slide horizontally; change items and they move vertically. In a default row, justify is the roughly horizontal axis (main) and items is the roughly vertical one (cross).
1
2
3
Now practice the classification. Drag each utility into the axis it controls. The tricky chips sound like they belong to one axis but belong to the other: self-end, for instance, is a cross-axis tool even though it doesn’t start with items.
Sort each flex utility into the axis it controls. Drag each item into the bucket it belongs to, then press Check.
justify-betweenjustify-centerjustify-evenlyjustify-startitems-centeritems-baselineitems-stretchself-endself-startSpacing items with gap
Section titled “Spacing items with gap”In this course, gap is how you space items inside a flex or grid container, and every flex container with more than one item gets a gap-*.
gap adds space between items and nowhere else: no leading gap before the first item, no trailing gap after the last, so it never collides with the container’s padding. The items sit flush against the padding edge with even gaps only in the interior. Use gap-x-* and gap-y-* to set the two axes independently; gap-y-* is the space between rows once items wrap.
gap is also why the ban on sibling margins costs you nothing: it does the spacing job those margins used to do. The full comparison with the old margin and space-x tricks comes later in this chapter. For now, reach for gap.
The min-w-0 fix every flex row needs
Section titled “The min-w-0 fix every flex row needs”This is the most common flexbox bug you’ll hit in real work, and the fix is a single utility. Commit it to memory.
You build a row with flex: an avatar on the left, a flex-1 middle region holding some text (a file name, an email subject, a URL), and a button on the right. It works until the text gets long. Then, instead of truncating with an ellipsis, the whole row grows wider than its container: the middle region refuses to shrink, so the button gets shoved off the edge or the row spills out of the card. flex-1 was supposed to make the middle flexible, so why won’t it shrink?
Flex items default to min-width: auto, which says never shrink an item below its min-content width. So even though flex-1 says shrink freely, that floor quietly overrides it and clamps the item at the width of its longest word. The item can’t go narrower, so it pushes everything else out instead. Images and form controls carry their own implicit min-content widths, so the same blowout happens with an <img> or an <input> in a flex row.
The fix lowers that floor. Add min-w-0 to the flexing item to override min-width: auto, and the item can finally shrink below its content width so the text truncates or wraps. Pair it with truncate, a text utility that clips the overflow to one line with an ellipsis. So the recipe for a flexible text region in a row isn’t flex-1 alone, it’s flex-1 min-w-0, plus truncate when you want the ellipsis. flex-1 alone is the trap.
The first tab below is the broken row, flex-1 only, with the long string spilling. The second adds min-w-0 truncate and the row behaves.
<div className="flex items-center gap-3"> <div className="size-10 shrink-0 rounded-full bg-violet-200" /> <p className="flex-1"> Re: invoice https://ledger.app/billing/invoices/2026-Q3-reconciliation-forecast </p> <button className="shrink-0 rounded-md bg-blue-600 px-3 py-1 text-white">Open</button></div>The row blows out. flex-1 says shrink freely, but the item’s default min-width: auto floor stops it at its longest unbreakable run, here the URL, so the text pushes the button off the edge instead of truncating.
<div className="flex items-center gap-3"> <div className="size-10 shrink-0 rounded-full bg-violet-200" /> <p className="flex-1 min-w-0 truncate"> Re: invoice https://ledger.app/billing/invoices/2026-Q3-reconciliation-forecast </p> <button className="shrink-0 rounded-md bg-blue-600 px-3 py-1 text-white">Open</button></div>One class lowers the floor. min-w-0 removes the min-content clamp so the item can shrink; truncate clips the overflow to an ellipsis. The avatar and button hold their size with shrink-0.
Now reproduce the bug and fix it yourself. The row below overflows; make the middle text truncate so the layout matches the target. The fix is the one class you just learned, plus truncate for the ellipsis.
This row overflows its card — the long subject pushes the Open button off the right edge. Add the one utility you just learned (plus truncate for the ellipsis) to the middle <p> so the subject text shrinks and truncates, and match the target.
Five flex layouts you’ll reach for
Section titled “Five flex layouts you’ll reach for”Experienced developers compose layouts from a small, settled set of patterns rather than reinventing the CSS each time. These five cover almost all the one-dimensional layout you’ll write in a web app, so learn them as whole units. Each tab pairs a live rendering with its class string, so you can read the pattern and the code together and inspect either in devtools.
<header className="flex items-center justify-between"> <div className="flex items-center gap-2.5"> <Logo /> <span className="font-extrabold">Ledger</span> </div> <nav className="flex items-center gap-4"> <a href="/pricing">Pricing</a> <a href="/docs">Docs</a> <button>Sign in</button> </nav></header><div className="flex items-center gap-3"> <label className="shrink-0">Workspace URL</label> <input className="flex-1 min-w-0" /></div><div className="flex flex-col gap-3"> <Card>Invite teammates</Card> <Card>Connect a bank account</Card> <Card>Set your billing currency</Card></div><div className="flex items-center gap-2"> <button>Bold</button> <button>Italic</button> <div className="flex-1" /> <button>Discard</button> <button>Publish</button></div><div className="flex flex-col h-full"> <header>Usage this month</header> <div className="flex-1">{/* body */}</div> <footer>Resets June 30</footer></div>Two of these lean on sizing utilities (h-full on the pinned-footer card, max-w-* on a real nav bar) that belong to a later sizing lesson; here they just let the patterns render. The toolbar uses leftover space as a layout tool: an empty flex-1 div between two clusters grows to fill the slack and pushes them apart, with no margins. The pinned footer is the same trick on the column axis: flex-col plus a flex-1 body region drives the footer to the bottom regardless of how much the body holds. Once you treat leftover space as something you place and grow on purpose, most “how do I push this down or over there” questions answer themselves.
Now style the unstyled nav bar from the start of the lesson to match the target: logo pinned left, actions pinned right, everything on one centered line. That is the flex items-center justify-between string from the opening paragraph, and you now know what each utility does.
Build the top nav bar from the intro. The logo sits on the left, the links and sign-in button on the right, everything on one centered line. Match the target with flex, an alignment utility, a distribution utility, and gap on the right-hand cluster.
The next lesson covers grid, the two-dimensional primitive for when one axis isn’t enough.
DevTools: the flex overlay
Section titled “DevTools: the flex overlay”When a flex layout looks wrong, look at what the browser is actually doing rather than guess at utilities. In the Elements panel of Chrome or Firefox, a small flex badge sits next to any flex container. Click it and the browser overlays the page with the main and cross axes, the gaps between items, and the bounds of each item.
This puts the two-axis model on screen. When items land somewhere you didn’t expect, you can read off which axis they’re aligned on instead of cycling through justify and items values hoping one sticks. An item that won’t shrink shows pinned at its content width; items bunched at one edge show where the free space went. Reach for it the moment a flex layout surprises you.
External resources
Section titled “External resources”Flexbox is best learned by playing with it. These let you keep moving the parameters around until the axis-and-property model is automatic.
The authoritative reference for the two-axis model: main vs cross, the container and the items.
Every flex property visualized in one page. The reference to keep open while you build.
An interactive game for drilling justify and items until the axis mapping is muscle memory.
A deep, interactive explainer, especially good on how the grow/shrink algorithm actually distributes space.