In What Circumstances Is Flex-Shrink Applied to Flex Elements and How Does It Work

In what circumstances is flex-shrink applied to flex elements and how does it work?

In order to see flex-shrink in action, you need to be able to make its container smaller.

HTML:

<div class="container">
<div class="child one">
Child One
</div>

<div class="child two">
Child Two
</div>
</div>

CSS:

div {
border: 1px solid;
}

.container {
display: flex;
}

.child.one {
flex: 1 1 10em;
color: green;
}

.child.two {
flex: 2 2 10em;
color: purple;
}
  • http://jsfiddle.net/GyXxT/ (unprefixed -- Opera or Firefox nightly build)
  • http://jsfiddle.net/GyXxT/1/ (webkit)

In this example, both child elements ideally want to be 10em wide. If the parent element is greater than 20em wide, the 2nd child will take twice as much leftover space as the 1st child, making it appear bigger. If the parent element is less than 20em wide, then the 2nd child will have twice as much shaved off of it as the 1st child, making it look smaller.

Current flexbox support: Opera (unprefixed), Chrome (prefixed), IE10 (prefixed, but uses slightly different property names/values). Firefox currently uses the old spec from 2009 (prefixed), but the new spec is supposed to be available in experimental builds right now (unprefixed).

Understanding flex-grow and flex-shrink when using flex-basis

Percentage lengths are relative to their containing blocks.

Therefore, if the flex container has a width of 200px, and the flex items are set to flex-basis: 50%, then each item will resolve to 100px.

Of course, in flex layout, flex-basis represents the initial main size or, the size before flex-grow and flex-shrink are applied.

You have flex-grow disabled, so nothing happens there.

But you have flex-shrink enabled, so the items will shrink below 100px when necessary to prevent an overflow of the container.

In this case, because all items are set to flex-shrink: 1, they will shrink in equal proportion.

article {  display: flex;  width: 200px;  border: 1px solid black;}
[one] > section { flex: 0 1 50px;}
[two] > section { flex: 0 1 50%;}
[three] > section { flex: 0 0 50%;}

/* non-essential demo styles */section { height: 50px; background-color: lightgreen; border: 1px solid #ccc; display: flex; justify-content: center; align-items: center; box-sizing: border-box;}
<p>container width 200px in all cases</p><article one>  <section><span>50px</span></section>  <section><span>50px</span></section>  <section><span>50px</span></section>  <section><span>50px</span></section></article>
<hr>
<p><code>flex-shrink</code> enabled</p>
<article two> <section><span>50%</span></section> <section><span>50%</span></section> <section><span>50%</span></section> <section><span>50%</span></section></article>
<hr>
<p><code>flex-shrink</code> disabled</p>
<article three> <section><span>50%</span></section> <section><span>50%</span></section> <section><span>50%</span></section> <section><span>50%</span></section></article>

When does flex-grow switch to flex-shrink, and vice-versa?

The flex-basis property sets the initial main size of the flex item, before free space is distributed by other flex properties.

This means that the flex item is first sized for its defined width (in flex-direction: row) or height (in flex-direction: column).

In other words, in a row-direction flex container, where flex-basis: 100px is equivalent to width: 100px, the item would have a width of 100px.

That's the initial main size.

THEN, other flex properties are applied. Namely, flex-grow and flex-shrink.

If the container is wider than 100px, then flex-grow: 1 expands the item to fill the extra space.

If the container is less than 100px, then flex-grow: 1 is ignored and flex-shrink: 1 reduces the size of the item according to a flex sizing algorithm.

So, to answer your question:

When does flex switch from grow to shrink?

Assuming both properties are enabled (i.e., they are not flex-grow: 0 or flex-shrink: 0), the switch will occur when the sum total of flex-basis / width / height on flex items is no longer less or more than the length of the container.

To make this a bit more clear:

  • When the sum total of flex-basis / width / height on flex items is no longer more than the length of the container, this means there is no overflow and the items don't consume all available space. flex-grow works.

  • When the sum total of flex-basis / width / height on flex items is no longer less than the length of the container, this means there is overflow and the items must shrink to fit inside the container. flex-shrink works.

From the spec:

flex-grow


This [property] specifies the flex grow factor, which determines how much the flex item will grow relative to the rest of
the flex items in the flex container when positive free space is
distributed.

flex-shrink


This [property] specifies the flex shrink factor, which determines how much the flex item will shrink relative to the rest of the flex
items in the flex container when negative free space is distributed.

flex-basis


This [property] specifies the initial main size of the flex item,
before free space is distributed according to the flex factors.

https://www.w3.org/TR/css-flexbox-1/#flex-property

How does flex-shrink factor in padding and border-box?

Flexbox defines this as

For every unfrozen item on the line, multiply its flex shrink factor
by its inner flex base size, and note this as its scaled flex
shrink factor
. Find the ratio of the item’s scaled flex shrink
factor to the sum of the scaled flex shrink factors of all
unfrozen items on the line. Set the item’s target main size to
its flex base size minus a fraction of the absolute value of the
remaining free space proportional to the ratio.

Simplifying, frozen flex items are those which can't or don't have to be flexed anymore. I will assume no min-width restrictions and non-zero flex shrink factors. This way all flex items are initially unfrozen and they all become frozen after only one iteration of the flex loop.

The inner flex base size depends on the value of box-sizing, defined by CSS2UI as

  • content-box: The specified width and height (and respective min/max properties) apply to the width and height respectively of the
    content box of the element. The padding and border of the element are
    laid out and drawn outside the specified width and height.

  • border-box: Length and percentages values for width and height (and respective min/max properties) on this element determine the
    border box of the element. That is, any padding or border specified on
    the element is laid out and drawn inside this specified width
    and height. The content width and height are calculated by
    subtracting the border and padding widths of the respective sides from
    the specified width and height properties. [...] Used values, as
    exposed for instance through getComputedStyle(), also refer to the
    border box.

Basically, that means that sizes (widths, flex bases) have an inner an an outer variant. The inner size includes only the content, the outer one also includes paddings and border widths. The length specified in the stylesheet will be used as the inner size in case of box-sizing: content-box, or as the outer one in case of box-sizing: border-box. The other can be calculated by adding or subtracting border and padding widths.

Neglecting lots of details, the algorithm would be something like

let sumScaledShrinkFactors = 0,
remainingFreeSpace = flexContainer.innerMainSize;
for (let item of flexItems) {
remainingFreeSpace -= item.outerFlexBasis;
item.scaledShrinkFactor = item.innerFlexBasis * item.flexShrinkFactor;
sumScaledShrinkFactors += item.scaledShrinkFactor;
}
for (let item of flexItems) {
let ratio = item.scaledShrinkFactor / sumScaledShrinkFactors;
item.innerWidth = item.innerFlexBasis + ratio * remainingFreeSpace;
}

With no paddings, it's as you explain

                                   (width)
innerW │ padd │ outerW
───────┼──────┼───────
300px * (1 + 2 / 1000px * -200px) = 180px │ 0px │ 180px
200px * (1 + 1 / 1000px * -200px) = 160px │ 0px │ 160px
100px * (1 + 2 / 1000px * -200px) = 60px │ 0px │ 60px
───────┼──────┼───────
400px │ 0px │ 400px

With 10px horizontal paddings, the available space is reduced by 3 * 2 * 10px = 60px, so now it's -260px.

                                   (width)
innerW │ padd │ outerW
───────┼──────┼───────
300px * (1 + 2 / 1000px * -260px) = 144px │ 20px │ 164px
200px * (1 + 1 / 1000px * -260px) = 148px │ 20px │ 168px
100px * (1 + 2 / 1000px * -260px) = 48px │ 20px │ 68px
───────┼──────┼───────
340px │ 60px │ 400px

When you use box-sizing: border-box, the specified flex bases are the outer ones, so the paddings are subtracted from them to calculate the inner ones, which are 280px, 180px, 80px. Then the sum of the scaled flex shrink factors becomes 2*280px + 180px + 2*80px = 900px. The available space is like in the case with no padding, because the outer flex bases are the same. Note the width retrieved by getComputedStyle will now be the outer one, so paddings are added back at the end.

                                                    (width)
innerW │ padd │ outerW
────────┼──────┼────────
280px * (1 + 2 / 900px * -200px) ≈ 155.6px │ 20px │ 175.6px
180px * (1 + 1 / 900px * -200px) = 140.0px │ 20px │ 160.0px
80px * (1 + 2 / 900px * -200px) ≈ 44.4px │ 20px │ 64.4px
────────┼──────┼────────
340.0px │ 60px │ 400.0px

How to calculate flex shrink when flex items have a different flex basis

Neglecting lots of details, the algorithm is something like this

let sumScaledShrinkFactors = 0,
remainingFreeSpace = flexContainer.innerMainSize;
for (let item of flexItems) {
remainingFreeSpace -= item.outerFlexBasis;
item.scaledShrinkFactor = item.innerFlexBasis * item.flexShrinkFactor;
sumScaledShrinkFactors += item.scaledShrinkFactor;
}
for (let item of flexItems) {
let ratio = item.scaledShrinkFactor / sumScaledShrinkFactors;
item.innerWidth = item.innerFlexBasis + ratio * remainingFreeSpace;
}

So the formula is like

flexBasis * (1 + shrinkFactor / sumScaledShrinkFactors * remainingFreeSpace)

First case

1*600px + 1*200px ─┐               width 
│ ───────
600px * (1 + 1 / 800px * -200px) = 450px
200px * (1 + 1 / 800px * -200px) = 150px
│ ───────
600px - (600px + 200px) ────┘ 600px

Second case

2*600px + 1*200px ──┐               width 
│ ───────
600px * (1 + 2 / 1400px * -200px) ≈ 429px
200px * (1 + 1 / 1400px * -200px) ≈ 171px
│ ───────
600px - (600px + 200px) ─────┘ 600px

Very confused about Flex Shrink and Grow

.container {  display: flex;  height: 200px;  border: 1px dashed black;}
.box1 { flex: 1 0 400px; /* flex-grow, flex-shrink, flex-basis */ max-width: 25%; background-color: green;}
.box2 { flex-grow: 1; background-color: orangered;}
<div class="container">  <div class="box1"></div>  <div class="box2"></div></div>

How can I make flex items shrink until wrapping is necessary, and then expand to fill the new space?

Here's a snippet that I believe achieves what you want:

<div>
<div class="container">
<div class="box">1</div>
<div class="box">2</div>
<div class="box">3</div>
<div class="box">4</div>
<div class="box">5</div>
</div>
</div>
<style>
.container {
display: flex;
max-width: 1200px;
flex-wrap: wrap;
}

.box {
outline: solid red 1px;
min-width: 400px;
flex-grow: 2;
/* flex: 2 0 50%; */ /* If you want to ensure that you never get more than 2 items per row */
}
</style>

In sum, the property you want is flex-grow: it ensures that the component will fill up available space on its row up to the specified factor (in this case 2, to go from 400px width up to 800px).

If it's important not to fit more than two elements on a row on wider screens, I added an alternative flex property for that which uses a flex-basis of 50% to make the components "greedier" in flowing onto a row than they would be otherwise.

How are heights of flex items calculated when flex-direction: column is applied?

You are running it a complex case where another specification need to be considered and also the min-height constraint.

When using flex:1 the browser is setting flex-basis: 0%. Pay attention to the % because all the trick is there. You have a percentage so you need a reference to resolve it and we are dealing with a height auto so the reference doesn't exist. At the end it's like you didn't set any flex-basis that's why you see no difference.

Now if you use flex-basis: 0px you will have a different behavior