Simplified flexbox algorithm

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.

Overview

  1. Initial layout:
  2. Wrapping: If the flex items don't fit on one line and 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.
  3. Shrinking: If the flex items don't fit on one line and no wrapping is allowed, flex items are allowed to shrink to the smaller of their min-content and the width value. If there is no width, items shrink to their min-content.
  4. Growing: If, after wrapping, any space is left on a line, and flex-grow is defined, the remaining space is divided among the flex items based on their flex-grow value.
  5. 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.
  6. 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.
  7. 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.

Initial layout

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.

Home Teamteam Our service

width only

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".

Home Teamteam Our service
Home Teamteam Our service

Here the width is so narrow that all items overflow.

Home Teamteam Our service

flex-basis only

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.

Home Teamteam Our service

Same for 30%.

Home Teamteam Our service

No text fits in 1% of the container width. Therefore all items stretch to min-content.

Home Teamteam Our service

Flex-basis and width

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.

Home Team Our service

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.

Home Team Our service
Rules

The rules are somewhat more complicated than the two examples above suggest. If both width and flex-basis are defined the following happens:

  1. If min-content is smaller than the flex-basis, the width is flex-basis.
  2. If 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.

Home Teamteamm Our service

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.

Home Teamteamm Our service

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.

Home Teamteamm Our service

Example 4: Now we use a measly width: 1%. This yields the same result as the previous example for the same reasons.

Home Teamteamm Our service

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.

Home Teamteamm Our service

The overflow effect

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.

Home Teamteam Our service

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.

Home Teamteam Our service

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.

Home Teamteamm Our service
Home Teamteamm Our service
overflow: clip

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.

Overflowing content not hidden

The next two examples should both hide overflowing content, but the second one doesn't hide it in some browsers.

Overflowing content not hidden

Wrapping

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.

Wrapping basics

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.

Home Team Our service tooLongText

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.)

HomeTeamOur servicetooLongText

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.

Home Team Our service tooLongText

Wrapping with flex-basis or width

The 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.

HomeTeamteamOur
service
HomeTeamteamOur
service

Therefore the wrapped version shows three items on each line.

Home Teamteam Our service Home Teamteam Our service

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.

HomeTeamteamOur
service
HomeTeamteamOur
service

Therefore the wrapped version shows four items on the first line and two on the second.

Home Teamteam Our service Home Teamteam Our service

Playground

Play around with a few wrapping flex containers here.

Home Team Our service tooLongText
Home Teamteam Our service Home Teamteam Our service
Home Teamteam Our service Home Teamteam Our service

Shrinking

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.

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.

Home Teamteam Our service Home Teamteam Our service

In the next example Teamteam is allowed to take 30% of the width. As a result the total line is wider.

Home Teamteam Our service Home Teamteam Our service

Flex-basis

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.)

Home Teamteam Our service Home Teamteam Our service

In these examples the value of flex-basis doesn't matter. All items still take their min-content width.

Home Teamteam Our service Home Teamteam Our service

Flex-basis and 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.

Home Teamteam Our service Home Teamteam Our service
Home Teamteam Our service Home Teamteam Our service

Second example

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.

Home Home Our service

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.

Home Home Our service

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.

Home Home Our service
Home Home Our service

Growing

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.

Nothing

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.

Home Teamteam

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.

Home Teamteam

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.

Home Teamteam

Flex-basis

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.

Home Teamteam Our service

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.

Home Teamteam Our service

Width

With width the same happens.

Home Teamteam Our service
Home Teamteam Our service

Flex-basis overrules width

Flex-basis overrules width. Thus, the next example is the same as the previous one. The width: 40% on the second item is ignored.

Home Teamteam Our service

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.

Home Teamteam Our service

Per line

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.

Home Teamteam Our service Home Teamteam Our service

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.

Home Teamteam Our service Home Teamteam Our service

Too large content

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%.

Home Teamteamm Our service

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%.

Home Teamteamm Our service

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.

Home Teamteamm Our service

min-width and max-width

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.

Home Teamteam Our service

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.

Home Teamteam Our service

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.

Home Teamteam Our service

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.

Home Teamteam Teamteam Our service

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.

Home Teamteam Our service

The exception: flex-basis and min-width

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.

Home Teamteam Our service

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.

Home Teamteam Our service
Home TeamteamTeamteam

Also, min-width overrules flex item shrinking. Flex items cannot become narrower than min-width.

Home Teamteam Our service Teamteam

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.

overflow on the container

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.

Home Teamteamm Our service Teamteam Teamteam
Home Teamteamm Our service Teamteam Teamteam
Home Teamteamm Our service Teamteam Teamteam

aspect-ratio

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.

Home Teamteam Our service Teamteam
Home Teamteam Our service Teamteam
Home Teamteam Our service Home Teamteamm Our service