`flex-grow` is weird. Or is it?
The following is a guest post by Manuel Matuzovic. It illustrates how flex-grow works, weird quirks and all. Then he goes into several examples on how common layout patterns may be implemented using flex-grow
and flex-basis
.
When I found out about flex-grow
, I made a simple demo to find out what it did and how it worked.
I thought I got everything figured out, but when I tried it on a website a colleague has recently made, nothing worked as expected. No matter what we did, the layout didn’t look and work like it did in my demo. That got me thinking and I started to doubt that I knew what flex-grow
was all about.
How flex-grow doesn’t work
Before we are going to take a deep dive into the functionality of flex-grow
, I want to explain to you what I got wrong at first.
I thought that all flex items with flex-grow
set to 1
would have an equal width. If one of the items had flex-grow
set to 2
that item would be twice as big as all the others.
This sounds great. It seems as if this is exactly what’s happening in the Pen I mentioned before. The parent element is 900px wide, the section with flex-grow: 2
has a calculated width of 600px and the aside element with flex-grow: 1
has a calculated width of 300px.
As you can see, everything works out perfectly in this demo, but it did not work at all in a real life example, even though we used the exact same CSS. As it turns out, the problem wasn’t the CSS, but the content (or lack of content). The demo I made works, because by only using two empty elements I created a test case that was too simple to depict the important specifics of the property.
How flex-grow
really works
I just described how flex-grow
does not work, but I showed you a demo that actually does do what I claim it does not (Later on in this article I will explain the reason for this.).
To clarify things, let me show you another Pen. We have got the exact same setup as in the first pen, but this time the section and aside elements aren’t empty. Now the ratio isn’t 2:1 anymore and the element with flex-grow set to 1 is actually bigger than the element with flex-grow set to 2.
Explanation
If we apply display: flex;
to the parent element and don’t change anything else, the child elements will be stacked horizontally, no matter what. If there isn’t enough space, they will shrink in size. If on the other hand there is more than enough space, they won’t grow, because Flexbox wants us to define how much they should grow. So rather than telling the browser how wide an element should be, flex-grow determines how the remaining space is distributed amongst the flex items and how big the share is each item receives.
Or in other words:
A flex container distributes free space to its items (proportionally to their flex grow factor) to fill the containers, or shrinks them (proportionally to their flex shrink factor) to prevent overflow.
https://drafts.csswg.org/css-flexbox/#flexibility
Demonstration
The concept is much easier to understand if we visualise it.
First we set the display
property of our parent element to flex
and by doing that our child elements become flex items and are positioned horizontally next to each other.
Next we decide how many shares of the extra space each element receives. In our previous example the first element receives 2/3 of the remaining space (flex-grow: 2
) and the second element 1/3 (flex-grow: 1
). By knowing how many flex-grow values we have in total, we know by which number to divide the remaining space.
Finally we have the number of distributable pieces. Each element receives the appropriate number of pieces based on its flex-grow
value.
Calculation
Theory and visual representations are nice, but let’s get dirty and do the math for the above example.
For our calculation we need 4 numbers: The parent width, the initial width of our section and our aside element and the total number of flex-grow values we’ll use.
parent width: 900px
section width: 99px
aside width: 623px
total flex-grow values: 3
1. First we have to calculate the remaining space
That’s pretty easy. We take the parents width and subtract the total initial width of every child element.
900 - 99 - 623 = 178
parent width – initial section width – initial aside width = remaining space
2. Next we have to determine how much one flex-grow
is
Now that we have the remaining space we need to determine into how many slices we want to cut it. The important thing here is that we don’t divide the remaining space by the number of elements, but by the number of total flex-grow values. So in our case that’s 3 (flex-grow: 2
+ flex-grow: 1
)
178 / 3 = 59.33
remaining space / total flex-grow values = “one flex-grow”
3. Finally the sliced up remaining space gets distributed between all elements
Based on their flex-grow values the section receives 2 slices (2 * 59.33) and the aside 1 (1 * 59.33). These numbers are added to the initial width of each element.
99 + (2 * 59.33) = 217.66 (≈218px)
initial section width + (section flex-grow value * “one flex-grow”) = new width
and
623 + (1 * 59.33) = 682.33 (≈682px)
initial aside width + (aside flex-grow value * “one flex-grow”) = new width
Easy cheesy, right?
Alright, but why does the first demo work?
We have the formula, now lets find out why we actually got the numbers we wanted in the first Pen even though we didn’t know what we were doing.
parent width: 900px
section width: 0px
aside width: 0px
total flex-grow values: 3
1. Calculate the remaining space
900 - 0 - 0 = 900
2. Determine how much one flex-grow is
900 / 3 = 300
3. Distribute the sliced up remaining space
0 + (2 * 300) = 600
0 + (1 * 300) = 300
If the width of each element is 0, the remaining space equals the actual width of the parent element and thus it looks like flex-grow
divides the parent element’s width in proportional parts.
flex-grow
and flex-basis
Just a quick recap: flex-grow
will take the remaining space and divide it by the total amount of flex grow values. The resulting quotient is multiplied by the respective flex-grow
value and the result is added to each child elements initial width.
But what if there’s no remaining space or what if we don’t want to rely on the elements initial width, but on a value we set? Can’t we still use flex-grow
?
Of course we can. There is a property called flex-basis
which defines the initial size of an element. If you use flex-basis
in conjunction with flex-grow
the way the widths are calculated changes.
This component sets the
flex-basis
longhand and specifies the flex basis: the initial main size of the flex item, before free space is distributed according to the flex factors.
https://drafts.csswg.org/css-flexbox/#valdef-flex-flex-basis
The big difference if we apply flex-basis
to an element is that we don’t use its initial width in our calculation anymore, but the value of its flex-basis
property.
I have adapted our previous example by adding flex-basis
to each element. Here’s the Pen for you.
parent width: 900px
section width: 400px (flex-basis value)
aside width: 200px (flex-basis value)
total flex-grow values: 3
1. Calculate the remaining space
900 - 400 - 200 = 300
2. Determine how much one flex-grow is
300 / 3 = 100
3. Distribute the sliced up remaining space
400 + (2 * 100) = 600
200 + (1 * 100) = 300
Just for the sake of completeness, you don’t have to use pixel values and hope for the best, percentages work as well.
Working with the box model
So to cover everything, let’s check out what happens if we add padding and margin. Nothing too special actually. In the first step of the calculation you just have to remember to subtract the margins as well.
The only thing to note is that in terms of box-sizing
flex-basis
behaves like the width
property. That means that the calculation as well as the results change if the box-sizing
property changes. If box-sizing
was set to border-box
, you would only work with the flex-basis
and margin
values in your calculation, because the padding
is already included in the width.
Some useful examples
Alright, enough with the math. Let me give you some examples of how you’re able to establish flex-grow
effectively in your projects.
No more width: [ x ]%
Due to the fact that the remaining space gets distributed automatically, we don’t need to think about width values anymore, if we want our child elements to fill the parent element.
See the Pen QyWEBb by Manuel Matuzovic (@matuzo) on CodePen.
The “Holy Grail” 3 column liquid layout with pixel-widths
Mixing fixed and fluid widths in column layouts is possible with float, but it is neither intuitive or easy nor flexible. Of course with Flexbox and a little flex-grow and flex-basis magic that is a piece of cake.
See the Pen jbRjMG by Manuel Matuzovic (@matuzo) on CodePen.
Filling remaining space with any element
If you, for example, have an input field next to a label and you want the input field to fill the rest of the space, you don’t need ugly hacks anymore.
See the Pen eJYdWV by Manuel Matuzovic (@matuzo) on CodePen.
You can find more examples on Philip Waltons Solved by Flexbox.
Listening to the specs
According to the specs, we should use the flex
shorthand rather than flex-grow
directly.
Authors are encouraged to control flexibility using the
flex
shorthand rather thanflex-grow
directly, as the shorthand correctly resets any unspecified components to accommodate common uses.
https://drafts.csswg.org/css-flexbox/#flex-grow-property
But be careful! If you just use flex: 1;
some of the above examples won’t work anymore, because the values set for the common uses don’t equal the default values and thus interfere with our needs.
If you want to use flex
for our use case, you should define it like this:
flex: 2 1 auto; /* (<flex-grow> | <flex-shrink> | <flex-basis>) */
Learning Flexbox
If you want to learn more about Flexbox, please check out these great resources:
- A Complete Guide to Flexbox by Chris Coyier
- Flexbox adventures by Chris Wright
- Flexbox Froggy by Thomas Park
- What the Flexbox? by Wes Bos
- flexboxin5
- Flexbox Tester by Mike Riethmuller
Summary and lessons learned
Is flex-grow
weird? Nah, not at all. We just have to understand how it works and what it does. If an element has flex-grow
set to 3
it does not mean that it’s 3 times bigger than an element that has flex-grow
set to 1, but it means that it gets 3 times more pixels added to its initial width than the other element.
I learned my lesson by testing flex-grow with two empty elements, which gave me a completely different understanding of what the property actually does and this of course led to wrong conclusions. You should check out new stuff in an environment that is as realistic as possible, to get the best impression of how it really works and behaves.
`flex-grow` is weird. Or is it? is a post from CSS-Tricks