image description

Maciej Nowakowski

Founder at Codecamps

6 Feb, 2018

3 Superpowers of the Flex-Box Model — How flex-basis, flex-shrink and flex-grow work.

The elements on the page are organised in the same way as words in a book: from left to right and from top to bottom.

If you have been using CSS for a while, you will have a good understanding of block and inline elements. You know how they work. But you may occasionally get baffled when you forget that some properties exist only on block elements and some on inline elements.

Block and inline elements in CSS

Before we dive into the flex-box model, leaving behind the memories of block and inline elements, let's quickly sum up what we know about them.

  • A block element always starts on a new line and takes up all available width of its parent. You can change its dimensions by assigning values to the width and height properties,
  • An inline element doesn't start at a new line. It takes only as much space as it needs in order to display its content.

There is one more difference between block and inline elements.

An inline element doesn't have width and height properties.

Try and change the dimensions of the span element below to: width: 100px; and height: 100px;.

What happened?

Nothing.

Change the span's display property to block, by adding display: block.

Now the span proudly presents its content as a block element. And block elements have both — width and height.

What if you wanted to change the dimensions of an inline element but didn't want it to move to the next line?

You could assign an inline-block value to the display property.

When an element becomes an inline-block element, it still behaves like an inline element — it doesn't jump to the next line — but you can change its dimensions. Let's try it.

Change the display property of the span element to display: inline-block; below:

Summary of Block and Inline Elements

  • A block element — starts on a new line, takes up all available width of the parent element. You can set its width and height properties.
  • An inline element — doesn't start on a new line. It doesn't have width and height properties.
  • An inline-block element — doesn't start on a new line but can be assigned width and height.

Flex-box model

What happens to an element when you change its display property to flex?

A new relation between the parent container and its children is established.

The parent container becomes a flex container. Its children become flex items. And it doesn't matter if they were block or inline elements in their previous life.

To illustrate this, let's create a layout that you wouldn't use in real-life. For visualisation purposes only.

Below, I placed two block elements, div and p, and an inline element, span, inside the inline element em. I hear you — complete mess.

Now, add display: flex to the container in CSS tab.

You've just created a new context and a new relationship between the parent element, the container, and the children elements with a class item.

The container has became a flex container. Its children have become flex items.

The flex container behaves like a block element. It takes up all available space. You can also change its width and height.

What about the flex items?

The flex items behave almost like inline-block elements. They don't start at the new line. They take only as much horizontal space as they need. You can set their width and height.

But they are like inline-block elements with superpowers.

They stretch to take up all vertical space.

Add height: 200px; to the container and see how the flex items grow vertically when the container's height changes.

The reason for that is that one of the container's properties, align-items is set to stretch by default.

Superpowers of Flex Items

After setting the container's display property to flex, its children (flex items) get three superpowers — flex-grow, flex-shrink, and flex-basis. These properties let you decide how much space flex items will take.

By default they are set to:

  • flex-grow: 0;
  • flex-shrink: 1;
  • flex-basis: auto;

Flex-basis

The flex-basis is the equivalent of the width property of an inline-block element.

By default, it's set to auto.

When it's set to auto, it sets the width of the flex item to the value of its width property. If the width property was not set, the element will take up only the necessary horizontal space to display its content. Like an inline-block element.

In the example below, add width: 100px; to the item class:

Now, all the items have the width of 100px.

But in the flex box model, it is a good practice to use flex-basis instead of width to set the element's width.

When you set the value of flex-basis to anything other than auto, the width property will be ignored.

Let's try it. Below the width of flex items is set to 100px. Add the flex-basis: 50px; to the item class:

Now, the width property is ignored and flex items take only 50px.

The flex-basis takes any CSS units. Try it, by setting the flex-basis to 25% above.

Now, each flex item takes 25% of its parent.

It's very important to understand how the 25% is calculated.

Let's set the width of the flex container to 400px and the flex items' flex-basis to 25%:

To understand how the width of flex items is calculated, start with the inner width of the flex container (parent element):

  • If the flex container's width is 400px, its padding is set to 10px, and the box-sizing is not set to border-box, the inner width equals 400px

  • If the flex container's box-sizing is set to border-box, its inner width is 400px width - 2 x 10px padding = 380px

Now , you can calculate the flex item's width. Let's take the first example — when the flex container's inner width is 400px:

  • If the flex item's box-sizing is not set to border-box, its width equals 400px * 25% = 100px. And its total width equals 100px + 2 x 10px padding = 120px

  • If the flex item's box-sizing is set to border-box, its total width equals 100px, and its inner width is 100px width - 2 x 10px padding = 80px

Let me show you an example. You may open the Developers Tools and inspect how the size of the flex container and flex items changes. It really helps.

Below, the flex container's width is set to 400px. It also has padding set to 10px. Because I didn't set the box-sizing to border-box, its inner width is 400px. And this value will be used to calculate the flex items width:

25% * 400px = 100px

Now, set the flex container's box-sizing to border-box. Its total width will be 400px and its inner width will drop to 400px - 2 x 10px padding = 380px. That's why the flex items shrank from 100px to 25% * 380px = 95px.

Remove the box-sizing line from the flex container and add padding: 10px; to the item class. The flex items grew. Each has now the total width of 120px. The inner width is still 100px or 25% of the flex container.

Notice that visually it doesn't make sense — three containers, each 25% wide take more than 3/4 of the flex container's width.

I hope that now you understand why there is nothing wrong with the flex box model. This is how the flex item's width is calculated.

What will happen when you add the box-sizing: border-box; to the item class?

The total width (inner width + padding) of the flex item shrinks from 120px to 100px. That's why flex items will take less space.

Let's do one more exercise — add the margin: 5px; to the item class. Nothing has changed. The flex item's width is still 100px (inner width + padding).

Finally, remove the box-sizing line from the item class. Now, the flex item's total width increased again to 120px:

100px + 2 x 10px padding = 120px

Flex Shrink

Until now, the maximum width of 3 flex items was 3 x 120px = 360px plus margins. The minimum width of the flex container was 380px. Flex items nicely fit into the flex container.

But what happens if we increase the width of flex items by changing their flex-basis property and set it to 200px? They should overflow the parent container. But — magically — they don't.

This is where the second superpower — flex-shrink — kicks in. By default, all flex items have the flex-shrink property set to 1. It means that flex items will shrink proportionally to fit in the flex container.

Let's create three flex items with the width of 200px. The total width of flex items is now 600px.

Since the flex container's width is only 400px, to fit in, flex items have to shrink. The excess 200px have to be shaved off. Because all the flex items have a flex-shrink set to 1 by default, they will shrink proportionaly by:

200px / 3 = 66.66px

So, their width will shrink to:

200px - 66.66px = 133.33px

You can inspect the flex items' width in the Developer Tools to see the exact width.

The flex-shrink property can take also higher values than 1. Let's set the flex-shrink of the .second flex-item to 2. Now, the excess of 200px will allocated proportionally to the flex-shrink value of a flex item.

In the CSS below I added flex-shrink: 2; to the .second class.

The second component shrank more than the first and the second one. The sum of flex-shrink values of 3 flex-items is 1 + 2 + 1 = 4.

That's why the excess 200px; is divided by 4.

The first component's width will be: 200px - 1 x 50px = 150px, the second 200px - 2 x 50px = 100px and the third one 200px - 1 x 50px = 150px.

You can play around with a flex-shrink by assigning different values to see how nicely flex-items shrink to fit in into the flex container.

Remember: a flex-shrink value tells you at what proportion the excess width is deducted from the original width of the flex items.

But there is one more important point — flex-shrink shaves off the excess width proportionally to the flex-shrink value, but also to the original width of the flex item.

Until now, flex items had the same width. Let's see what happens if flex items have different width and different flex-shrink values assigned.

Below, the first and the third component have the width of 200px and the flex-shrink of 2. The second component is only 100px wide and its flex-shrink is set to 1.

The total width of all flex items is 2 x 200px + 100px = 500px;. It means that the excess width is 500px - 400px (flex container) = 100px and it needs to be trimmed off proportionally to the flex-shrink value, and to the flex items original width.

Let's find out what are the calculations the flex model does to find out by how much to shrink flex items.

At first, it divides the width of each flex item by the width of the smallest flex item. The smallest flex item is 100px wide. So, for the first flex item, the ratio will be 200px / 100px = 2. For the second flex item, it equals 100px / 100px = 1. The third flex item will have a ratio of 2 because it has the same width as the first one.

In the next step, the flex-box multiplies the ratio by the value of flex-shrink. In our example:

first flex item: 2 x 2 = 4
second flex item: 1 x 1 = 1
third flex item: 2 x 2 = 4

The sum of above numbers is 4 + 1 + 4 = 9.

Next, flex box calculates by how much it should shrink each flex item. Assuming that the overflowing width of the three flex items is 100px:

first flex item: 4/9 * 100px = 44.44px
second flex item: 1/9 * 100px = 11.12px
third flex item: 4/9 * 100px = 44.44px

Finally, the results are deducted from the original width of each flex item. This is how we get the final widths:

first flex item: 200px - 44.44px = 155.56px
second flex item: 100px - 11.12px = 88.88px
third flex item: 200px - 44.44px = 155.56px

Let's do a quick check: 155.56 + 88.88 + 155.56 = 400px. Perfect fit!

Flex-grow

When you change the display property of an element to flex, its children become flex items with the flex-grow set to 0. They take only as much space as it is necessary to display their content.

The flex-grow property gives you another superpower. It allows you to increase the width of flex items so they nicely expand and take all available space of their parent — the flex container.

In the example below, I set the flex-grow to 1 in the item class. All components expanded to absorb all available space.

The available space is the difference between the inner width of the flex container and the sum of widths of the flex items' content.

Here, I have placed additional span elements so we can exactly measure the content's width in Developers Tools:

The widths of the flex items' content are: 56.44px, 10.67px and 21.31px respectively. That sums up to 88.42px.

The remaining available space 400px - 88.42px = 311.58px will be divided by 3 and allocated to flex items (3 flex items have flex-grow set to 1). That's why each flex item will grow by 103.85px.

And so you get the following widths:

first flex item: 160.29px
second flex item: 114.52px
third flex item: 125.16px

That's 399.99px. The tiny error comes from the rounding.

You can use the flex-grow property to figure out proportion at which the flex items will absorb the available space.

Let's set the flex-grow of the second item to 3:

It may surprise you, that the second flex item didn't get three times wider than its siblings.

The available space will be divided by the sum of flex-grow values of all three flex items.

1 + 3 + 1 = 5

And the flex items will grow by:

first flex item: 1/5 * 311.58px = 62.32px;
first flex item: 3/5 * 311.58px = 186.95px;
first flex item: 1/5 * 311.58px = 62.32px;

Final widths of flex items:

first flex item: 56.44px + 62.32px = 118.76px;
first flex item: 10.67px + 186.95px = 197.62px;
first flex item: 21.31px + 62.32px = 83.63px;

Minimum and Maximum Widths

Sometimes, you will want to have more control over the flex items' width. Sometimes, you will want them to grow but not beyond a certain value. And sometimes, you will want them to shrink, but not below a specific width.

As soon as you start using the flex-basis to define the width of a component, the width property is ignored. The min-width and max-width properties set the width boundaries of an element.

In the example below, I set the min-width of all flex items to 100px. Although they have the flex-basis set to 50px, the flex-basis will be ignored. And the flex items will be 100px wide.

Now, if you resize the window, the sidebar width will remain in the range of 100-300px. When min-width and/or max-width are set, the flex-basis property works only in the range between these two values.

The min-width and max-width are extremely useful when used with percentages to set the flex-basis.

In the example below, I set the sidebar and the main-area to 30% and 70% respectively.

Additionally, I set the sidebar min-width and max-width to 100px and 300px respectively, to make sure that its width remains in the range of 100-300px.

Resize the window to see, how nicely the sidebar's width will adjust. But at the same time it stays in the specified range.

Rules to remember when using flex-basis, min-width, and max-width:

  1. When the flex-basis is set to auto (default) and a width of the flex item is not set, the flex item takes up only as much the space as it is necessary to display its content
  2. When the flex-basis is set to auto and you set the width, the flex item will use the width property to determine its width
  3. When you set a flex-basis of the flex item to anything other than auto, the width property will be ignored and the width set by the flex-basis will be used
  4. The flex-basis is taken into account when it falls between the min-width and the max-width
  5. If the flex-basis is set to a lower value than the min-width, an element will have a min-width
  6. If the flex-basis is set to a value that is higher than the max-width, the element will have a max-width

Flex

You can use the 3 superpowers of flex items separately, like in the previous examples, or you can define them in one line using the flex property following the pattern below:

flex: flex-grow-value flex-shrink-value flex-basis-value;

For example, instead of:

flex-grow: 1;
flex-shrink: 1;
flex-basis: 25%;

you can use the abbreviated syntax:

flex: 1 1 25%;

The flex property can be assigned several predefined values. So you can use them as long as you understand what will happen to your flex items:

  1. flex: initial; — equivalent of flex: 0 1 auto;. This is the default value assigned to flex items, when you set display: flex on their parent element.

    flex-grow: 0;
    flex-shrink: 1;
    flex-basis: auto;

    A flex item is sized according to its width and height properties (because of flex-basis: auto;). It shrinks to fit into a flex container but doesn't grow to take up all available space.

  2. flex: auto; — equivalent of flex: 1 1 auto;

    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: auto;

    A flex item is sized according to its width and height properties. It grows to take up all available space and it shrinks to fit into a flex container

  3. flex: none - equivalent of flex: 0 0 auto

    flex-grow: 0;
    flex-shrink: 0;
    flex-basis: auto;

    The element behaves like an inline element. You can set its width and height and it will not grow or shrink. If the width and height are not set, it takes up only as much width as it is necessary to display its content.

Summary

It is crucial to understand underlying calculations of the three flex-box model superpowers to be able to figure out how the flex-box model will behave. It will save you time and frustration in the future.

Some calculations are counterintuitive. For example, when you set the flex-basis to 25%, the three components took much more space than expected. Now you understand why.

Here are the main takeaways from the article:

  • Use the flex-basis instead of the width property to control the width of a flex item. Once you set the flex-basis to anything other than auto, the width property is ignored.
  • If you set the min-width and/or max-width of the flex items, the width set by flex-basis will be taken into account only within the min-width - max-width range
  • Flex items shrink proportionally to their original width and their flex-shrink value
  • Flex items grow in proportion to their flex-grow value

In the next interactive guide, you will build a "Sweet Pumpkins" web application with React. "Sweet Pumpkins" will help you to find the next movie to watch.

If you enjoyed this article,

get the next one in your inbox:

© 2017 Level Eleven Web Solutions Ltd.

Company Registered No. 10509511

16 Palace Gates Road

London N22 7BN, UK

email: maciej@codecamps.com