Flexbox lays out items along a main axis and a perpendicular cross axis. display: flex creates the container, flex-direction chooses which way the main axis points, justify-content distributes space along it, and align-items positions items across the cross axis. Modern browsers support the standard syntax without vendor prefixes. The horizontal/vertical examples below assume the usual horizontal writing mode.
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.
For current browser targets, use display: flex. Support for obsolete browsers is a separate compatibility decision; do not copy old prefixed syntaxes into a modern layout by default.
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 */
}row follows the inline axis of the writing mode, and column follows the block axis. In a horizontal writing mode, flex-direction gives these directions:
.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 */
}Step through it and watch the boxes move. Each line you add is highlighted as the layout responds:
Four plain block-level <div>s. With no flex, each takes a full line and stacks top to bottom.
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). Walk through each value and see where the leftover space goes:
flex-start is the default: items pack to the start of the main axis, and any leftover space sits at the end.
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. Same boxes, same container, only align-items changing:
stretch is the default. With no explicit height, every item stretches to fill the cross axis, so the row comes out equal-height for free.
baseline aligns their text baselines, rather than the bottom edge of each text box, 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 positive free space. With equal zero bases and without different minimum-size constraints, equal grow factors produce equal main sizes. Different content minimums, padding or borders can affect the visible result.flex-shrinkweights how items give up space when the container is too small. The algorithm also scales the weight by the flex base size.0prevents flex shrinkage; minimum and maximum size constraints still apply.flex-basisis the starting main size before flexing. Browsers commonly expandflex: 1to1 1 0%, while the specification defines an omitted basis as0. That distinction matters in an indefinite-size container. Use an explicit basis when it matters.flex: 1 1 autostarts from the main-size property or content; it does not make that starting size a minimum.
Watch how flex redistributes the row as you add each declaration, from natural widths to equal columns to a weighted split to a fixed sidebar:
By default items take their natural content width and do not grow. The extra space in the row goes unused.
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 the automatic minimum commonly uses a content-based minimum for a non-scrollable item. Other constraints, including aspect ratios, can affect the precise value. 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 main 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. Step from the broken state to the fix and watch the long URL stop blowing out the row:
A flex item's default min-width is auto, which resolves to its min-content size. The long unbreakable URL cannot shrink below its own width, so it blows out of the container.
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 uses a preferred starting width, and items that do not fit move to another line. A basis is not a hard minimum; an item can still shrink when the container itself is narrower.
.card {
flex: 1 1 240px; /* start at 240px, wrap as needed, and allow shrinking */
}Here is that whole sequence, one declaration at a time, ending in a responsive card grid with no media queries:
Start with a plain flex row. By default everything stays on one line and the items shrink to fit, however cramped that gets.
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 */
}Dial the gap up and notice it only ever lands between items, never against the dashed container edges:
No gap: the items sit flush against each other.
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. Use gap for consistent spacing in current browsers. Margins still serve other layout purposes, particularly grouping items with an auto margin. The next section shows that distinction.
Push one item to the far end with an auto margin
For a left-to-right navigation row, margin-left: auto absorbs positive free space before an item, pushing it and the following items toward the end. margin-inline-start: auto expresses the same grouping in the writing mode's logical direction:
<nav class="nav">
<a href="/">Home</a>
<a href="/blog">Articles</a>
<a class="account" href="/account">Account</a>
</nav>.nav {
display: flex;
align-items: center;
gap: 1rem;
}
.nav .account {
margin-inline-start: auto;
}A plain flex row packs every item to the start. The brand and all the nav links sit together on the left, with the free space stuck at the end.
The fixed gap remains between neighboring items. The auto margin absorbs the rest of the available space on its axis before justify-content runs. If there is no positive free space, an auto margin cannot manufacture it; decide how the navigation should wrap or collapse on a narrow screen.
Spread items with space-between, space-around or space-evenly
.bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}space-between places the first and last items at opposite edges and distributes remaining space between them. It is useful for two groups, such as a logo and a set of links. With only one item, there is no space to distribute between items and it aligns at the start.
space-around assigns equal space around each item, making the edge spaces half an internal space. space-evenly makes the spaces equal, including the outer edges. Those descriptions assume enough positive free space; overflow invokes the relevant fallback alignment.
Use an auto margin when one group should separate from the others. Use gap when the spacing should stay fixed. Use space-between when every space between items should participate in the distribution.
Replace an empty spacer div
An empty element with flex-grow: 1 can create a visual gap, but it adds markup for a job an auto margin normally handles. Put the margin on the first item of the second group instead. A decorative pseudo-element is possible when a layout genuinely needs another flexible box, but it is unnecessary for the navigation example above.
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 */
}Box 2 breaks ranks while the rest hold the container's alignment:
The container sets align-items: flex-start, so every item sits at the top of 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 */
}Watch the boxes shuffle as the order values change, while their numbers (their source position) stay put:
Every item has order: 0 by default, so they render in source order: Box 1 through Box 4.
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 items with automatic cross sizes stretch to the height of their own flex line. Different wrapped lines can have different heights.
.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 */
min-width: 0; /* allow wide content to shrink */
}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.
FAQ
justify-content distributes items along the main axis; align-items aligns them along the cross axis. In the default flex-direction: row, the main axis is horizontal and the cross axis is vertical, so justify-content is your horizontal control and align-items is your vertical control. Switch to flex-direction: column and the axes flip, but the rule stays the same.
Browsers commonly expand it to 1 1 0%: grow, shrink and basis. The specification defines the omitted basis as 0, so use an explicit basis if that difference affects your layout. Equal growth does not override different minimum-content sizes or box decoration. flex: 1 1 auto uses a size or content-based starting point, not a minimum floor.
Use gap. It puts space only between items, never on the outer edges, so you avoid the "remove the margin on the last child" hack entirely. gap works in current browsers. The only common case where margins still win is pushing one item to the far end with margin-left: auto.
A flex item's automatic minimum often comes from its content, so the item refuses to shrink below that floor even with flex-shrink: 1. Content that cannot wrap (a long unbroken string, a wide image, a <pre> block) then overflows the container and triggers a horizontal scrollbar. Set min-width: 0 on the item (or min-height: 0 in a column layout) to allow it to shrink past its content, and pair it with overflow: hidden plus text-overflow: ellipsis if you want text to truncate cleanly.
Grid, for the overall page. The rule of thumb is flexbox for one dimension (a row or a column of items) and grid for two (rows and columns at once). Use grid for the page skeleton and flexbox inside each region for components like nav bars and card rows. They are designed to work together, not as competitors.
No. Flexbox is fully Baseline and prefix-free in 2026. The -webkit-, -ms-flexbox, and old display: -webkit-box syntax you see in older tutorials is obsolete, as are flexbox polyfills. Write the standard spec and ship it.
See also
- Center anything in CSS: the full set of centering techniques, including where flexbox is and is not the right tool.
- 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.
- CSS Flexbox specificationdrafts.csswg.org
- MDN flex shorthand and browser behaviordeveloper.mozilla.org
- MDN flex alignment and auto marginsdeveloper.mozilla.org
- MDN gap referencedeveloper.mozilla.org





