Flexbox lays out the children of a container along one line, or several lines if they wrap, and decides how much of the free space each child gets. The rules are in CSS Flexible Box Layout Module Level 1, a Candidate Recommendation Draft. display: flex works in Chrome 29, Edge 12, Firefox 20 and Safari 9 and later, and gap in flex containers from Chrome 84, Firefox 63, Safari 14.1 and iOS Safari 14.5 (MDN browser-compat-data 8.1.4).

Most flexbox confusion comes from three places: the flex shorthand, the automatic minimum size that stops items from shrinking, and the difference between align-items and align-content. This guide goes through the spec sections behind them and checks each rule in a browser. All measurements were taken on 2026-10-02 in Chrome 152 on macOS, by applying the declarations to test elements and reading getComputedStyle() and getBoundingClientRect().

Main axis and cross axis

A flex container has two axes (§2). The main axis runs in the direction set by flex-direction; the cross axis is perpendicular to it.

flex-directionMain axis (horizontal-tb writing mode)Items start at
row (initial)left to rightthe left edge
row-reverseright to leftthe right edge
columntop to bottomthe top edge
column-reversebottom to topthe bottom edge

Every alignment property is named after one of the axes. justify-content positions items along the main axis; align-items and align-self position them on the cross axis; align-content positions whole lines on the cross axis. So in a row container justify-content: center centres horizontally, and in a column container it centres vertically.

“Left to right” holds for horizontal English text. The axes follow the writing mode: in a right-to-left language row starts at the right, and in vertical writing it runs top to bottom.

The reversed values change only where the boxes are drawn. With row-reverse, three 100px items in a 600px container landed at x = 500, 400 and 300px, but the Tab key and screen readers still follow the HTML order. Section 5.4 tells authors not to use reordering in place of correct source order.

The flex shorthand: what flex: 1 and flex: auto actually do

Each item has three flex properties (§7):

  • flex-grow: how much of the positive free space it takes, as a ratio.
  • flex-shrink: how much of the overflow it gives up when there is not enough room.
  • flex-basis: the size to start from before growing or shrinking. auto means “use width, or the content size if width is auto”.

The flex shorthand sets all three. The spec lists the common values in §7.1.1:

ShorthandExpands toEffect
flex: initial (the default)0 1 autoContent-sized; shrinks but never grows
flex: auto1 1 autoContent-sized, then shares the extra space
flex: none0 0 autoContent-sized, neither grows nor shrinks
flex: 1 (any positive number)1 1 0Starts from zero, so sizes follow the ratio

To see the difference, three items whose content was 50, 100 and 150px wide were placed in a 600px row:

ItemsWidths in Chrome
no flex set50, 100, 150px
flex: auto150, 200, 250px
flex: 1200, 200, 200px
flex: 1, flex: 2, flex: 1150, 300, 150px

With flex: auto the 300px of free space was split equally and added to each content width. With flex: 1 the basis is zero, so all 600px is free space and the widths are equal. That is why flex: 1 is the usual way to get equal columns. For the item that flex: 1 sets, Chrome reported flex-grow: 1, flex-shrink: 1 and flex-basis: 0%.

How flex-shrink takes space away

When the bases add up to more than the container, items shrink. The amount is not split equally: §9.7 weights each item’s flex-shrink by its flex base size (the “scaled flex shrink factor”), so large items give up more.

Two empty items with flex-basis: 400px and 200px in a 300px container overflow by 300px:

SettingWidths in Chrome
both flex-shrink: 1200, 100px
first flex-shrink: 2160, 140px
second flex-shrink: 0100, 200px

In the first row each item lost half its basis (400 × 1 and 200 × 1 share 300px of overflow in a 2 : 1 ratio). With flex-shrink: 2 on the first item the weights became 800 and 200, so it lost 240px and the second lost 60px. flex-shrink: 0 is how you keep an icon, an avatar or a fixed sidebar from being squeezed.

min-width: auto: why a flex item will not shrink

The examples above used empty boxes. Real items have content, and §4.5 gives a flex item an automatic minimum size: unless it is a scroll container, it will not shrink below its content-based minimum, roughly the longest word, URL or fixed-width child it contains. The initial value of min-width is auto, and that is what this resolves.

A 300px row held an item containing an unbreakable 600px child and a 100px flex: none sibling:

Item styleItem widthContainer scroll width
default (min-width: auto)600px700px
min-width: 0200px600px
overflow: hidden200px300px

By default the item stayed at its content width and pushed the sibling out of the container. min-width: 0 let it shrink to the 200px it was given, but the child still overflowed the item. overflow: hidden made the item a scroll container, whose automatic minimum is zero, and clipped the child. For text the usual fix is min-width: 0 on the item plus overflow-wrap: anywhere or text-overflow: ellipsis on the text.

The same rule explains why flex: 1 columns are sometimes unequal. Three flex: 1 items in 600px, one containing a 400px-wide element, came out at 100, 400 and 100px: the middle item could not go below 400px, and the other two shared what was left. In a column container the same applies to min-height.

Positioning on the main axis: justify-content and auto margins

justify-content distributes the space left over after flexing (§8.2). With three 100px items in a 600px row, Chrome placed them at:

justify-contentLeft edges of the items
flex-start (behaves as the initial normal)0, 100, 200px
center150, 250, 350px
flex-end300, 400, 500px
space-between0, 250, 500px
space-around50, 250, 450px
space-evenly75, 250, 425px

space-between puts all the space between the items, space-around gives each item equal space on both sides (so the ends get half as much as the gaps), and space-evenly makes every gap, including the ends, the same.

Auto margins act before justify-content (§8.1): an auto margin absorbs all the free space on its side. That is the simplest way to push the last items of a navigation bar to the right:

.nav { display: flex; gap: 16px; }
.nav .push { margin-left: auto; }

In an 800px container with a 120px logo and two 80px links, the first of which had margin-left: auto, the items sat at 0, 624 and 720px. The 16px gap still applied between the links. To centre a single item in both directions, margin: auto on the item gives the same result as justify-content: center plus align-items: center on the container: a 100 × 40px item in a 400 × 200px container landed at (150, 80) both ways.

Wrapping and align-content

flex-wrap: nowrap (the initial value) keeps every item on one line, even if they overflow. wrap starts a new line when the next item does not fit. On each line align-items aligns the items; across lines align-content aligns the lines themselves (§8.4).

Section 6 makes a distinction that trips people up: a container with flex-wrap: wrap is a multi-line flex container even when all its items fit on one line, and a nowrap container is single-line. The spec says that in a single-line container “align-content has no effect”. So:

  • In a 200px-tall container with flex-wrap: wrap, align-items: flex-start, align-content: center and three 40px items on one line, the line moved down to y = 80px.
  • The same settings with nowrap left the items at y = 0.

The initial value of align-content is normal, which behaves as stretch in a flex container: the lines grow to share the container’s height. Six 150 × 40px items in a 400 × 200px wrapping container form three lines:

align-contentTop edge of each line
normal (initial)0, 66.66, 133.34px
flex-start0, 40, 80px
space-between0, 80, 160px

With normal, items without a set height would also stretch to the 66.66px line height, because align-items defaults to normal, which acts as stretch.

gap instead of margins

gap sets the space between items and between lines without adding space at the container’s edges. Its two values are the row gap and the column gap. With flex-wrap: wrap, gap: 20px and four 100px items in a 340px container, the first three sat at x = 0, 120 and 240px and the fourth wrapped to (0, 30px): 10px of item height plus the 20px row gap. Doing the same with margins needs negative margins on the container to cancel the outer edges.

Flexbox handles one direction at a time: each line is sized on its own, so items on different lines do not line up unless their widths are fixed. When things must align in both rows and columns, such as a card grid or a form with a label column, CSS Grid is the better fit; the CSS grid guide covers it. Flexbox remains the simpler choice for a navigation bar, a button group, a toolbar or a row of tags.

Writing the container with the generator

The CSS flexbox generator writes the container rule only. It has drop-downs for flex-direction, flex-wrap, justify-content, align-items and align-content, a text field for gap, and an Items field (1–8) for the preview boxes. Every property is written out, so you can delete the lines you do not need. On load it shows:

.container {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: normal;
  align-items: normal;
  align-content: normal;
  gap: 1rem;
}

These are the CSS initial values, so the rule changes nothing until you pick another value. In a flex container align-items: normal and align-content: normal behave as stretch, and justify-content: normal behaves as flex-start (CSS Box Alignment Level 3). To pack wrapped lines at the top instead of stretching them, choose flex-start for align-content (0, 40, 80px in the example above instead of 0, 66.66, 133.34px).

The navigation bar from earlier, with the items centred vertically:

.container {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: space-between;
  align-items: center;
  align-content: normal;
  gap: 16px;
}

A list of tags that wraps, with the lines packed at the top, 8px between lines and 12px between tags:

.container {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: normal;
  align-items: normal;
  align-content: flex-start;
  gap: 8px 12px;
}

The tool has no controls for the item properties, so flex, flex-grow, flex-shrink, flex-basis, order, align-self and min-width: 0 have to be added by hand. justify-content offers normal and the six values in the table above (no start, end or stretch), and align-content has no space-evenly. The gap field is copied as typed without validation; an invalid value ends up in the output, and the browser drops that declaration.