Back to the index.
Written in March - April 2023.
This test page explains a simplified version of the flexbox algorithm that is enough to understand about 95% of practical use cases. No doubt there will be edge cases somewhere that break these rules, but I can't do everything.
I found the rules contained in this page by reverse engineering, and not by reading the spec. The spec is fairly unreadable as specs go, and besides it is confirmed that browser implementations do not follow the spec in one instance — and possibly in a second one, but I don't have confirmation on that.
Therefore this overview shows flexbox as it is actually implemented in browsers.
Here's the best explanation of the algorithm I found so far, by Tiffany Brown. It doesn't treat everything I want to know, though, so I rolled my own.
width
or flex-basis
is defined, all items get width: max-content
.width
is defined, all items get that width.flex-basis
is defined, all items get that flex-basis. Then, if overflow
is visible
(or, in Firefox and Safari 15 or older only, clip
), items are allowed to grow to min-content
if their contents overflow.width
and flex-basis
are defined, all items get the flex-basis
and width
is ignored. However, if their content overflows they are allowed to stretch to the larger value of width
and flex-basis
.flex-wrap: wrap[-reverse]
is defined, the first flex item to overflow the flex container is wrapped to the next line. Repeat as often as necessary.
min-content
and the width
value. If there is no width
, items shrink to their min-content
.flex-grow
is defined, the remaining space is divided among the flex items based on their flex-grow value.min/max-width
work as expected, except that a combination of min-width
and flex-basis
causes browsers to follow the width
model, and not the flex-basis
model.overflow
on the container works as expected: the flexbox uses the container width for layout, and if items overflow they're handled by the overflow
as usual.aspect-ratio
on items works as expected: the items attempt to take on the correct aspect ratio, but their height is constrained by the minimum height the content needs. If the content is too high the aspect ratio is not obeyed. This is in line with aspect-ratio
being a weak declaration.Initially we'll study content that fits on one line. We'll get back to too-long content later.
If neither flex-basis
nor width
is defined all items get width:
.max-content
If a width
is defined, all flex items get that width. Their content may be wrapped if necessary, as with "Our service", or it may overflow the item, as with "Teamteam".
Here the width
is so narrow that all items overflow.
If flex-basis
is defined, all flex items get the larger of the defined flex-basis
and min-content
. Thus, if necessary flex items stretch up beyond their defined width
to contain all of their content.
Home and Our Service fit in 25% of the container width (Our Service with text wrapping, but that's fine). Teamteam does not, and stretches up to min-content
.
Same for 30%.
No text fits in 1% of the container width. Therefore all items stretch to min-content
.
flex-basis
overrules width
. In the next example all items have flex-basis: 25%
and the second one has width: 40%
. The width
is ignored.
In the next example all items have width: 25%
and the second one has flex-basis: 40%
. The width
is accepted for the first and third item, but overruled by the flex-basis
for the second item.
The rules are somewhat more complicated than the two examples above suggest. If both width
and flex-basis
are defined the following happens:
min-content
is smaller than the flex-basis
, the width is flex-basis
.min-content
is larger than flex-basis
, the width is the smaller of min-content
on the one hand, and the larger of width
and flex-basis
on the other.(I established these rules by reverse engineering. I have no clue if this is what the spec says should happen. But it's what's happening — at least in the examples below.)
Example 1: Home and Our Service are smaller than flex-basis
, so they get a 25% (flex-basis
) width. Teamteamm is larger, so it is allowed to stretch up to 30% (width
, which is larger than flex-basis
). That's still not enough, and the content overflows.
Example 2: We stretch up to width: 50%
. flex-basis
still determines the width of Home and Our Service, but Teamteamm is now allowed to stretch up to 50%. It doesn't need that much to reach min-content
, though; about 37% is enough.
Example 3: Again Home and Our Service take flex-basis
(30%). Teamteamm is still too large, so it also
takes 30%, since flex-basis
is larger than width
here. The content still overflows.
Example 4: Now we use a measly width: 1%
. This yields the same result as the previous example for
the same reasons.
Example 5: Now we shrink to flex-basis: 1%
. None of the items have a min-content
smaller than the flex-basis
, so they all take min-content
. However, the upper bound is still 30%, as the larger of width
and flex-basis
, so the Teamteamm still grows to 30% and overflows.
So far we used the default overflow: visible
for all examples. What happens if we change that? width
is easy. As you'd expect, the overflowing part of Teamteam is hidden when you set overflow: hidden
.
However, it turns out that the flex-basis
behaviour changes: items no longer stretch to min-content
if necessary, but instead stick to the defined flex-basis
.
When both flex-basis
and width
are defined, overflow: [not-visible]
also causes the flex items to take on their exact flex-basis
in all circumstances. Overflowing content is hidden.
overflow: hidden | auto | scroll
cause the min-content
rule of flex-basis
to be suppressed.
overflow: clip
also does so in most browsers, but in Firefox, as well as in Safari 15 and lower, clip
behaves the same as visible
and does not suppress the min-content
rule.
This is possibly an old bit of behaviour that is being changed. My old Safari 12/Mac and 15/iOS do the same as Firefox, but my new Safari 16/Mac does not. Maybe a future Firefox will fall in line with the other browsers as well.
This example should not hide overflowing content.
The next two examples should both hide overflowing content, but the second one doesn't hide it in some browsers.
So far, the content fit on one line. Now we're going to look at what happens if it doesn't.
If the content is too long for one line, flexbox has two options: wrap the content to multiple lines, or shrink individual items. Wrapping is something you have to set explicitly with flex-wrap: wrap[-reverse]
. If you don't set it, shrinking takes place.
Let's set flex-wrap: wrap
. After initial layout, any flex item that overflows the flex container is wrapped to the next line. This process is repeated if necessary.
This can be slightly confusing because wrapping takes place before shrinking. The example below is the same flexbox, once without and once with wrapping.
It might appear that wrapping is unnecessary, but that's not true.
Wrapping takes place at the time the Our Service item still has its initial width of max-content
. Picture it like this. (Here I faked stage 1 of the layout process; the box below is not a flexbox.)
tooLongText is pushed out of the flex container and is wrapped to the next line. Among other things that means that shrinking is unnecessary and Our service retains its width: max-content
.
flex-basis
or widthThe width
/flex-basis
rules we found earlier remain in force. In this flex-basis
example Teamteam is allowed to stretch up to min-content
, and as a result takes more than 25% of the width. That pushes the other items rightward, and the first wrap point is the second Home.
Therefore the wrapped version shows three items on each line.
Compare this to items with width: 25%
. Teamteam strictly takes one quarter of the container width and the text overflows its item if necessary. The first wrap point comes at the second Teamteam link.
Therefore the wrapped version shows four items on the first line and two on the second.
Play around with a few wrapping flex containers here.
If flex items overflow their container and wrapping is not allowed, the algorithm shrinks flex items.
The intended rule is that flex items are allowed to shrink to the smaller of min-content
and the defined width
.
If we use width
this is exactly what happens. In this example most items have a min-content
smaller than 25%, and they shrink to min-content
. The two Teamteams have a min-content
larger than 25%, and stay at 25%. That's not enough, and the content still overflows, but the algorithm is satisfied.
In the next example Teamteam is allowed to take 30% of the width. As a result the total line is wider.
If we use flex-basis
only the algorithm still tries to find a defined CSS width
, doesn't find any, compares min-content
to undefined
and goes with the min-content
. Therefore some flex items are wider in the flex-basis
example than in the width
example: they take a width of min-content
.
(This is a confirmed bug in the flexbox implementation of all browsers. I do not expect this bug to ever be solved; by now there are plenty of flexbox layouts that depend on this behaviour.)
In these examples the value of flex-basis
doesn't matter. All items still take their min-content
width.
If both width
and flex-basis
are defined the algorithm finds a width
, uses it, and we get the same result as when only width
is defined. Defining flex-basis
is pointless here.
For the second example we'll take some content that is clearly too wide for its flex item: the image. In the width
model it stretches up its item to the allowed maximum of 30%. That's not enough, and the content overflows the item. The other items shrink just enough to allow the 30%-wide item to fit on the line. The two Home links don't have to shrink all the way to min-content
, shrinking to about 23% each is enough to fit the last item in the flex container.
In the flex-basis
model the last item is stretched up to contain its content. Again the other items shrink, and now they need to shrink more than in the previous example, since the last item is now wider. Therefore the two Home items shrink to their min-content
as well. This is still not enough, and the final item overflows the flex container, but the algorithm has done its best.
If both flex-basis
and width
are defined
the official rule is followed: flex items are allowed to shrink to the smaller of min-content
and the defined width
(and not flex-basis). That's what happens here. The other flex items shrink equally to make room for the final item. Defining a flex-basis
is pointless.
Growing takes place if there's space left on a line and you defined flex-grow
. Obviously, either shrinking or growing takes place, not both. There's either too little or too much space left.
The rule is simple: we take the items' defined widths, calculate how much space is left, and divide that among the items relative to their flex-grow
value. Thus all items together will take 100% of the container width.
In the first example the items have width: max-content
, and they thus have a different width. About 49% of the container width is left.
If the items grow this 49% is divided equally among the two items, so each gets about 24%. Note that the items do not have the same width now. Teamteam starts out with a larger width than Home, and both gain 24%, so Teamteam remains larger than Home.
If one item gets flex-grow 1 and the other 3, the 51% is divided in a 1:3 ratio, so that the first gets about 13% and the second about 38% in addition to their base width.
When a width is defined, either through flex-basis
or through width
, that definition is used for the grow calculations. This matters for flex-basis. Take this example. As we saw earlier, in the flex-basis
model the second item becomes wider because it takes min-content
as its minimum.
However, the grow algorithm uses the defined flex-basis
of 25%, and not the actual width of about 30%. It then adds equal amounts of space, and as a result all items are equally wide.
With width
the same happens.
Flex-basis overrules width
. Thus, the next example is the same as the previous one. The width: 40%
on the second item is ignored.
In the next example the flex-basis: 40%
overrules the width: 25%
, and the second item has a larger base width and thus remains larger than the other two after growing.
Growing takes place after wrapping, and is done for each line individually. So the wrapping algorithm in stage 2 decides how the items are wrapped, and then the growing algorithm decides how items on individual lines are grown.
With width
, the wrapping algorithm divided the items over two lines 4-2. Thus, the items on line 1 are grown to 25% each, and those on line 2 to 50% each.
With flex-basis
, the wrapping algorithm divided the items over two lines 3-3. Thus, the items on both lines are grown to 33% each.
One final edge case: what if the content is too large even for the grown item? Here the second item grows to a min-content
of more than 33%.
If we grow the items, the left and right items grow as much as they can, but the middle one doesn't. It is effectively excluded from growth since it's already larger than 33%.
Compare this to width
, where the middle item overflows and doesn't take any extra space. After growing each item takes exactly 33%, and the middle one still overflows.
In a flexbox context min-width
and max-width
mostly work as you'd expect: they constrain the effective width of flex items. Here flex items get width: max-content
, but at least 25%. The Home item is normally smaller than 25% but now takes that value. This is exactly what you'd expect.
Here flex items get width: max-content
, but at most 30%. The last two items are now capped at that width. This is exactly what you'd expect. Content may overflow.
Here flex items get width: max-content
, but at least 25% and at most 30%. The first one has a 25% width, the other two a 30% width. This is exactly what you'd expect. Content may overflow.
Here shrinking takes place. As you'd expect, shrinking stops at width 30%, so the first and last item remain wider than they otherwise would be.
max-width
overrules flex-grow
. If you use both items will grow, but not beyond max-width
. This is exactly what you'd expect. Content may overflow.
There's one exception where min-width
does not behave as you'd expect.
If an item has a flex-basis
and a min-width
, the flex-basis
is treated as if it is width
: the item is not allowed to stretch to its min-content
.
You can see that below by adding min-width: 25%
to these examples.
The Teamteam item stretches up to its min-content
, as we've seen many times already ... until you add a min-width
to the mix. Then the flex-basis
definition is adhered to rigidly, and the Teamteam items no longer stretch to their min-content
.
Also, min-width
overrules flex item shrinking. Flex items cannot become narrower than min-width
.
This is fairly hard to see. Once you add the min-width: 25%
the items above are shrunk, but to a minimum of 25%. That's why the Home item actually becomes larger: its min-content
is less than 25%. So min-width overrules shrinking.
Because you add min-width, flex-basis
is also re-interpreted as width
, which means that the Teamteam items do not grow beyond their min-width of 25% and effectively become narrower than without the min-width.
Setting an overflow
to the flex container has no effect on the sizing of the flex items: they use the width of the container for layout, as usual.
aspect-ratio
in flexbox works pretty much as you'd expect. It is a weak declararion; it allows itself to be overruled by any adverse condition.
In practice that means that the flex item's height may be constrained to a certain minimum by the text's height. As long as that's not the case the items retain their aspect-ratio
, but as soon as the height should become less than the content height, browsers drop aspect-ratio
, as they should.