Go back to home page of Unsolicited Advice from Tiffany B. Brown

How Flexbox Works

Boxes flexing
Boxes flexing

Unlike many parts of CSS, Flexible box layout or Flexbox can be difficult to understand just by using it. It wasn't until this most recent update of CSS Master that I even sort of grokked it.1 I hope to untangle its mysteries in this post.

Flexbox was designed to distribute elements and space within a containing element. Unlike CSS Grid which aligns objects vertically and/or horizontally, Flexbox works in a single direction: a row or a column.

To distribute space, first the browser has to determine how much space is available. Browsers use the following process, more or less.2

  1. Calculate the available space within the flex container. That's the main size of the container minus the size of its margins, padding, borders, and gaps.
  2. Calculate the flex base size and hypothetical main size of each flex item, using the value of flex-basis, min-width, or the size of the flex item's contents — whichever is greater. Flex base size is the minimum size that a flex item requires. Hypothetical main size refers to the size of a flex item before the flex factors are applied. The flex base size of a flex item will never be smaller than that of its contents.
  3. Calculate the total hypothetical main size for all flex items.
  4. Compare the total hypothetical main size of all flex items to the available space within the flex container. When total size exceeds available space, use the value of flex-shrink as the flex used factor. When total size is less than the available space, use the value of flex-grow as the flex used factor. Browsers use either the value of flex-shrink or flex-grow for each line of flex items.

The flex used factor determines what proportion of the leftover space to add or subtract from each flex item. Browsers complete the calculation for each flex item, in a loop.

Leftover space is what remains after subtracting the sum of items with a definite size, plus padding, and gap values from the interior width of the flex container. Think of definite size as the known size of a flex item. When a flex item has a definite size, it's considered an inflexible item. Items that do not have a definite size are considered flexible items. Only flexible items have their size adjusted by the flexible box layout algorithm. This space adjustment is called flexibility.

I think Firefox’ Flexbox inspector currently does the best job of explaining how Flexbox works. Chrome and Edge indicate whether a flex item has grown or shrunk, however, Firefox also tells you by how much. I recommend using it to inspect the examples in this post.

In all of the following examples, the flex container is 1200 pixels wide.

A simple Flexbox example

Figure 1 uses the initial property value for flex0 1 auto. The flex property is a short hand for the flex-grow, flex-shrink and flex-basis properties. Here:

  • the value of flex-grow is zero;
  • the value of flex-shrink is 1; and
  • the value of flex-basis is auto.

Because the value of flex-basis is auto, the browser uses the max-content value as the base size of each flex item. The total hypothetical main size of these flex items is less than the amount of space available within the flex container. As a result, these flex items neither grow to fill the available space, nor shrink to fit within it.

Figure 1: A simple Flexbox example. You can also view Figure 1 in another tab.

Before continuing, let's take a detour into the syntaxes of the flex property.

A detour into the syntaxes of the flex property

flex accepts at least one value, and as many as three.

When you use one-value syntax, the value gets interpreted as flex: <number> 1 0, where <number> is the flex-grow value. In other words, flex: 2 is equivalent to flex: 2 1 0.

When you use two-value syntax, the first value is interpreted as the flex-grow value. However:

  • If the second value is a number, it's interpreted as the flex-shrink value
  • If it is a valid value for the width property, it's interpreted as the flex-basis value.

In other words, flex: 1 0 is the same as writing flex: 1 0 0, but flex: 1 30rem is equivalent to flex: 1 1 30rem.

When you use three-value syntax:

  • The first value is the flex-grow value.
  • The second value must be a number. It's the flex-shrink value.
  • The third value must be a valid value for the width property. It's interpreted as the flex-basis value.

Think of flex-basis as a starting size. When a flex item has been set to grow, browsers add flexibility to the flex-basis value. When it's been set to shrink, browsers subtract flexibility from this value.

When little bitty flex items grow up

Figure 2 shows what happens when you add flex: 1 to every flex item (the equivalent of flex: 1 1 0).

Figure 2: After adding flex: 1 to every flex item. You may also view Figure 2 as a standalone document.

Here the total hypothetical main size of our items is less than the interior space of the container. As a result, the browser uses flex-grow as the flex used factor. Every flex item in this example has a flex-grow value of 1, and a flex-basis value of zero.

Browsers loop through each flex item to determine its flexibility using the following calculation:

flexibility = (free space ÷ sum of the reamining flex items' flex-grow value) × flex-grow

Adding or subtracting the flexibility amount to or from the value of flex-basis gives us the element's final size. Let's calculate the flexibility of item A. Remember that our container is 1200 pixels wide.

( 1200 ÷ ( 1 + 1 + 1 + 1 + 1 ) ) × 1 = 240

Item A has a flexibility of 240 pixels. Next, add this flexibility value to its flex-basis to determine its size: 0 + 240 = 240.

According to this math, item A should be 240 pixels. However, Antidisestablishmentarianism is a long, unhyphenated word that requires 417 pixels of space3. Instead of setting the size of A to 240 pixels, the browser clamps its width to its minimum content size of 417 pixels. Now there are 783 pixels of space to distribute across items B, C, D, and E.

For item B, the calculation looks like this (remember that 0 is the value of flex-basis for every flex item:

( 783 ÷ ( 1 + 1 + 1 + 1 ) ) × 1 = 195.75 0 + 195.75 = 195.75

Subtract the size of item B from the remaining amount of space: 783 - 195.75 = 587.25. That leaves roughly 587 pixels to allocate to items C, D, and E.

C: 0 + ( 587 ÷ ( 1 + 1 + 1 ) ) × 1 ) = 195.67 D: 0 + ( 391.33 ÷ ( 1 + 1 ) ) × 1 = 195.665 E: 0 + ( 391 - 195.665 ÷ 1 ) × 1 = 195.335

The browser deducts the allocated space from the remaining space then calculates the flexibility and size of the next flex item.

How much space the contents of a flex item require depends on:

  • the value of the font-size property;
  • the height and width of the font's glyphs;
  • the length of particular words and lines of text; and
  • the intrinsic or specified size of elements, such as images and video, contained by the flex item.

Variations in how browsers and operating systems render text also play a role.

flex-grow and proportion

In Figure 3, item C has a flex value of 5 (flex: 5).

Figure 3: How flex-grow allocates space proportionately. Open Figure 3 in a new tab.

Its siblings have a flex: 1 declaration. Here's the CSS.

div > :not([id=c]) {
  flex: 1;
}

[id=c] {
  flex: 5;
}

Remember that flex: 1 is the equivalent of flex: 1 1 0 and flex: 5 is the same as flex: 5 1 0. Every flex item in this example is set to grow from a flex-basis of zero. However, the flex-grow value of 5 means that item C should receive roughly five times as much flexibility as its siblings.

Here too, Antidisestablishmentarianism forces item A to have an inline size of 417 pixels. That leaves 783 pixels to allocate proportionally across items B, C, D, and E.

Let's calculate the size of item B. There are four remaining flexible items, and the flex-grow value of C is 5. That means we'll divide 783 by 8, which is the sum of each flexible item's flex-growvalue. We'll then add that to our flex base size of 0.

0 + ( 783 ÷ ( 1 + 1 + 5 + 1 ) ) × 1 = 98

Item B is roughly 98 pixels wide. Subtracting 98 from 783 leaves about 685 pixels of space reamining in the container. Now let's calculate the size of items C, D, and E.

C: 0 + ( 685 ÷ ( 5 + 1 + 1 ) ) × 5 = 490 D: 0 + ( 195 ÷ ( 1 + 1 ) ) × 1 = 97.5 E: 0 + ( 97.5 ÷ 1 ) × 1 = 97.5

Since item C has a flex-grow factor of 5, it receives five times as much flexibility as items B, D, and E.

When flex-basis is auto

In Figure 4, the item C still has a flex-grow value of 5. However, for each sibling element, the used value of flex is its initial value: 0 1 auto.

Figure 4: How flex-grow works when flex-basis: auto is applied. You may wish to view Figure 4 in a separate tab.

In the previous example, items A, B, D, and E have a flex-grow value of 0. Because their flex-basis value is auto, their inline size will be as wide as their content demands. For item A, that's the size of Antidisestablishmentarianism and its bold-letter A. For items B, D, and E, it's the size of each letter.

Items A, B, D, and E all have a definite size. Right away, we can determine how much space remains in the container:

1200 - ( 417 + 33 + 35 + 30 ) = 685

Since item C is the only flexible item in the container, it grows to fill the remaining space.

0 + 685 = 685

Again, I've rounded some of these numbers. Your browser's Flexbox inspector may report slightly different values.

When flex items shrink

Let's look at an example of a Flexbox layout when flex-shrink is the flex used factor. In Figure 5, every flex item has a flex-basis value of 500 pixels. Items B, C, D, and E all have a flex-shrink value of 1 and a flex-grow value of 0. Item A has a flex-shrink value of 5.

Figure 5: When the browser uses flex-shrink, and the flex-shrink value is greater than 1. View Figure 5 in a new tab.

Here, the total hypothetical main size for all flex items is 2500 pixels — the sum of the flex items' flex-basis values. Since the total hypothetical main size is larger than the size of our container, the browser uses the flex-shrink factor.

Let's start by calculating the flexibility of item A. We'll start with the difference between the hypothetical size of our flex items and our flex container.

( 1300 ÷  ( 5 + 1 + 1 + 1 + 1 ) ) × 5 = 722.22

Next, we need to subtract this amount from the value of flex-basis.

500 - 722.22 = -222.22

That's a negative value. If item A was an empty element, its main size would be clamped to zero pixels. Instead, the browser clamps item A to its minimum content size — about 34 pixels.

Since the size of item A is now known, we can subtract its size from the remaining space in the container: 1200 - 34 = 1166. Now we can calculate the size of each remaining flex item.

B: ( 1166 ÷ ( 1 + 1 + 1 + 1 ) ) × 1 = 291.5 C: ( 874.5 ÷ ( 1 + 1 + 1 ) ) × 1 = 291.5 D: ( 583 ÷ ( 1 + 1 ) ) × 1 = 291.5 E: ( 291.5 ÷ 1 ) × 1 = 291.5

Something to note: when flex-grow and flex-shrink both have a property value of zero, flex items neither grow nor shrink. Should the total hypothetical main size of all flex items exceeds that of the flex container, the flex items will stack along the main axis without wrapping. They may also overflow the container. You can change this behavior by setting the value of flex-wrap to wrap or wrap-reverse.

Let's look at how browsers determine flexibility across multiple lines.

Flexbox and flex-wrap

In Figure 6, I've changed the value of flex-wrap from its initial value of nowrap to wrap. The flex property values are unchanged from Figure 5.

Figure 6: Adding flex-wrap: wrap causes boxes to wrap if the total hypothetical main size exceeds that of the container.View Figure 6 in a new tab.

When the value of the flex-wrap property is wrap or wrap-reverse, the browser uses each item's flex-basis value to determine how many items can fit on a line. Flex items wrap at the point at which the sum of their hypothetical main size exceeds that of the container.

In Figure 6, flex-grow: 0. As a result, there's 200 pixels of space remaining at the end of each of the first two lines. Changing the flex-grow value to 1, on the other hand, creates the result shown in Figure 7. Each flex item grows to fill the remaining space in its line.

Figure 7: When flex-wrap: wrap is applied to the flex container and flex-grow is greater than zero, flex items expand to fill the remaining space. View Figure 7 in a new tab.

Setting flex-wrap to wrap-reverse works similarly. However, wrap-reverse also reverses the flex items' visual rendering order.

Conclusion

Unlike other areas of CSS layout, Flexbox can be difficult to understand by using. Here's what to keep in mind.

  • Flexbox distributes space in a single direction — along a row or a column.

  • The flex-basis value determines the starting or base size of a flex item. However, the intrinsic size of a flex item's contents can force a flex item to be larger than the value of flex-basis.

  • For any line of flex items, the browser uses either the value of flex-grow or flex-shrink — not both at once.

  • Whether the browser uses flex-grow or flex-shrink as the flex used factor depends on how much space is available along the main direction.

  • Available space gets distributed across flex items proportionately, based on the value of each items used flex factor.


  1. If we're being really honest, I'm not 100% sure I do. 

  2. The specificaton includes a much more jargon-heavy, technical explanation of the Flexbox algorithm. 

  3. How much space text requires depends on the length of non-hyphenated, non-breaking words, type size, and the dimensions of each glyph in the chosen font. In the associated demos, I've used IBM Plex Sans for consistency. 

  4. Firefox, on some platforms or with some add-ons, may report a flexibility value of -772 pixels or something that seems a little bit wrong. The thing to know is this: whenever the hypothetical main size of a flex item is less than the size of its contents, its used size will be the minimum content size. 

Get CSS Master

Did you learn something from this blog post? You might like the latest edition of CSS Master. It contains several nuggets of CSS joy like this one. Buy now from SitePoint