If you remember one thing about flexbox, make it this: every flex container has a main axis and a cross axis, and almost every property you set is just answering "along which axis, and how do I distribute the space." Get that picture straight and the rest of flexbox stops being a pile of property names to memorize and becomes two questions you can answer on sight. This guide is built around that model, then walks through the properties and the handful of layouts I actually reach for.
Flexbox is fully Baseline and prefix-free in 2026. You do not need -webkit-, -ms-flexbox, the old display: -webkit-box syntax, or a polyfill. Write the modern spec and ship it.
The mental model: main axis vs cross axis
You turn an element into a flex container with display: flex. Its direct children become flex items and lay out along the main axis. The cross axis runs perpendicular to it.
.container {
display: flex;
/* flex-direction: row; <- the default */
}flex-direction decides which way the main axis points:
.container {
flex-direction: row; /* main axis -> horizontal (default) */
/* flex-direction: column; main axis -> vertical */
/* flex-direction: row-reverse; horizontal, reversed */
/* flex-direction: column-reverse; vertical, reversed */
}This is the part that trips people up: when you switch to flex-direction: column, the main axis becomes vertical and the cross axis becomes horizontal. Every property that targets "the main axis" now works top-to-bottom, and the one that targets "the cross axis" now works left-to-right. Nothing else about the properties changes, only which direction they point. Hold the axes in your head and column layouts stop feeling like a different system.
justify-content (main axis) vs align-items (cross axis)
These two properties are where most of the confusion lives, and the axis model dissolves it:
justify-contentdistributes items along the main axis.align-itemsaligns items along the cross axis.
In the default row direction, that means justify-content is your horizontal control and align-items is your vertical control:
.container {
display: flex;
justify-content: center; /* main axis: pack items to the horizontal center */
align-items: center; /* cross axis: center them vertically */
}justify-content takes flex-start (the default), flex-end, center, space-between, space-around, and space-evenly. The space-* values only do something when there is leftover space on the main axis, which is exactly how you build a nav bar (more on that below).
align-items takes stretch (the default), flex-start, flex-end, center, and baseline. stretch is why a row of items with different content heights all come out the same height for free: each item stretches to fill the cross axis. baseline lines items up by the bottom of their first line of text, which is the right choice when you have labels of different font sizes sitting next to each other.
The reason align-items: center is the modern answer to vertical centering is exactly this: it is a cross-axis alignment, and in a row the cross axis is vertical. I cover the full set of centering techniques, including the cases flexbox is not the best fit for, in center anything in CSS.
The flex shorthand: grow, shrink, basis
By default flex items take their natural content size and do not grow to fill extra space. flex changes that. It is shorthand for three properties:
.item {
flex: 1; /* flex-grow: 1; flex-shrink: 1; flex-basis: 0%; */
}flex-growis a unitless weight for how leftover space is divided.flex: 1on every child makes them share the row equally. Give one childflex: 2and it takes twice the share of the extra space.flex-shrinkis the weight for how items give up space when the container is too small.1(the default) lets them shrink;0refuses to shrink belowflex-basis.flex-basisis the starting size before grow/shrink kicks in.flex: 1sets it to0%(the spec keyword expands to1 1 0%, which is why browsers report0%rather than a bare0), so children size purely by their grow weight.flex: 1 1 autokeeps the content's natural size as the floor.
In practice I write flex: 1 constantly (one number is "grow to fill, share equally") and reach for the explicit three-value form only when I need a fixed starting width:
.sidebar {
flex: 0 0 280px; /* don't grow, don't shrink, stay 280px */
}
.content {
flex: 1; /* take everything else */
}The min-width: auto gotcha (why an item refuses to shrink)
This is the flexbox bug report I have filed against my own code more than any other: a flex item, often one holding a long unbroken string, a <pre> block, or a wide image, blows out of its container and forces a horizontal scrollbar even though flex-shrink: 1 says it should shrink. The cause is that a flex item's default min-width is auto, not 0, and min-width: auto resolves to the item's min-content size. Shrinking stops at that floor, so an item whose content cannot wrap (a 60-character URL with no break opportunities) simply will not get smaller, and it overflows instead.
The fix is to set the minimum explicitly so the item is allowed to shrink past its content:
.item {
min-width: 0; /* row layouts: let it shrink below content size */
/* min-height: 0; column layouts: the same fix on the cross axis */
}If the overflowing content is text, pair min-width: 0 with overflow: hidden and text-overflow: ellipsis (or overflow-wrap: anywhere) so the now-shrinkable item actually truncates or wraps rather than clipping. This one rule explains the majority of "my flexbox is overflowing and I do not know why" cases, and it is the first thing I check when a row scrolls sideways.
flex-wrap: let items drop to the next line
By default flex items all sit on one line and shrink to fit, which can squeeze them past the point of usefulness. flex-wrap: wrap lets them flow onto new lines instead:
.cards {
display: flex;
flex-wrap: wrap;
}Pair flex-wrap: wrap with a flex-basis and you get a responsive grid of cards with no media queries: each card claims a minimum width, and as many as fit sit on a row, the rest wrap.
.card {
flex: 1 1 240px; /* grow to fill, but never narrower than 240px before wrapping */
}You can also write flex-flow: row wrap as shorthand for flex-direction plus flex-wrap in one line.
gap: the modern replacement for margins between items
For years the only way to space flex items apart was margins, with the awkward "but not the last one" dance (:last-child { margin-right: 0 } and similar). gap replaces all of that:
.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem; /* even spacing between items, no edge gap */
/* gap: 1rem 2rem; row-gap column-gap if you want them different */
}gap only puts space between items, never on the outer edges, so you do not need a spacer hack and you do not have to null out the last child. gap in a flex container reached Baseline in April 2021 and is safe to use without fallbacks today. This is the single biggest reason older flexbox tutorials look dated: anything that spaces items with margin on each child predates gap. Use gap. The one related case where you genuinely still want margins, pushing one item to the far side with margin-left: auto, I cover in pushing flex items apart with auto margins, gap, and space-between.
align-self: override one item's cross-axis alignment
align-items sets the cross-axis alignment for every item. align-self overrides it for a single item:
.container {
display: flex;
align-items: flex-start;
}
.container .promoted {
align-self: center; /* this one item centers on the cross axis */
}One gotcha worth knowing: because align-self works on the cross axis, what it does flips when you change flex-direction. In a nested column container the cross axis is horizontal, so align-self: center centers that item left-to-right, not top-to-bottom. When align-self behaves "backwards," check which direction the parent's axis points, not the property.
order: visual reordering (with a caveat)
order changes the visual position of an item without touching the HTML. Default is 0; lower numbers come first, and negative values are allowed:
.featured {
order: -1; /* render first, regardless of source position */
}The accessibility caveat is real and non-negotiable: order changes only the visual order, not the DOM order. Screen readers and keyboard tab order still follow the source. If you reorder content that a user reads or tabs through, you create a mismatch between what they see and what they navigate. Keep order for purely cosmetic shuffles; if the reading order genuinely changes, change the HTML.
Common layouts
Here are the three I build most often. None of them need a media query for the basic behavior.
Nav bar (logo left, links right). Put the links in their own group and let justify-content: space-between push the two groups to opposite ends:
.nav {
display: flex;
align-items: center; /* vertically center logo and links */
justify-content: space-between;
gap: 1.5rem;
}Card row (equal-height, wrapping). This is the classic "equal-height columns" problem that used to need tables or JavaScript. With flexbox it is free: align-items: stretch is the default, so every card matches the tallest one.
.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.cards > .card {
flex: 1 1 280px; /* equal share, wrap below 280px */
}Sidebar plus content. A fixed sidebar and a fluid main column is the textbook flex: 0 0 / flex: 1 pairing:
.layout {
display: flex;
gap: 2rem;
}
.layout > .sidebar {
flex: 0 0 280px; /* fixed width */
}
.layout > .content {
flex: 1; /* takes the rest */
}When to reach for grid instead
A fair point from the old comment threads: flexbox is not the tool for full-page layout. The rule of thumb is flexbox for one dimension, grid for two. Flexbox lays out items along a single axis (a row or a column) and is perfect for components: nav bars, button groups, card rows, toolbars. CSS Grid lays out in rows and columns at once and is the right tool for the overall page skeleton: header, sidebar, content, footer placed on a defined grid. They compose well together, a grid for the page, flexbox inside each region, so it is not either/or. Reach for flexbox when you are distributing items along one line, and grid when you are placing things into a real two-dimensional structure. The newer :has() selector pairs nicely with both when you want a container to restyle itself based on what is inside it.
See also
- Center anything in CSS: the full set of centering techniques, including where flexbox is and is not the right tool.
- Pushing flex items apart: margin-auto, gap, and space-between: the one place margins still beat gap, and the no-spacer-div way to push items to the far edge.
- The CSS
:has()selector: style a container based on what it contains, which pairs well with both flexbox and grid.
Sources
Authoritative references this article was fact-checked against.
- Basic concepts of flexbox: MDNdeveloper.mozilla.org
- justify-content: MDNdeveloper.mozilla.org
- align-items: MDNdeveloper.mozilla.org
- flex shorthand: MDNdeveloper.mozilla.org
- gap: MDNdeveloper.mozilla.org
- flex-shrink and the min-width: auto floor: MDNdeveloper.mozilla.org
- Compat 2021: flex gap shipped in all browsers (web.dev)web.dev





