flex-grow not sizing flex items as expected

Short Analysis

The problem is that rows 1-3 have two horizontal margins and row 4 only has one.

enter image description here

With horizontal margins at 10px each, row 4 has 10px more free space than the other rows. This throws off the alignment of the columns.

Because flex-grow applies only to free space, and is heavily influenced by content and margins, it’s not the most secure way to size flex items.

Try flex-basis instead. Add this to your code:

.button    { flex-basis: 33.33%; }
#number0   { flex-basis: calc(66.67% + 10px); }
*          { box-sizing: border-box; }

.numbers {
    display: flex;
    flex-direction: column;
}

.row {
    display: flex;
    flex-direction: row;
    flex-grow: 1;
    justify-content: space-between;
}

.button {
    display: flex;
    flex-basis: 33.33%;
    justify-content: center;
    align-items: center;
    margin: 5px;
    border-radius: 5px;
    border: 1px solid gray;
    background: rgba(255, 255, 255, 0.2);
    cursor: pointer;
}

#number0   { flex-basis: calc(66.67% + 10px); }
*          { box-sizing: border-box; }
<div class="numbers">
    <div class="row">
        <div class="button number" id="number1">1</div>
        <div class="button number" id="number2">2</div>
        <div class="button number" id="number3">3</div>
    </div>
    <div class="row">
        <div class="button number" id="number4">4</div>
        <div class="button number" id="number5">5</div>
        <div class="button number" id="number6">6</div>
    </div>
    <div class="row">
        <div class="button number" id="number7">7</div>
        <div class="button number" id="number8">8</div>
        <div class="button number" id="number9">9</div>
    </div>
    <div class="row">
        <div class="button number" id="number0">0</div>
        <div class="button" id="colon">:</div>
    </div>
</div>

Extended Analysis

You wrote:

It seems that the content inside a flex div affects its calculated size concerning the flex-grow property. Am I doing something wrong?

The source of your problem is not the content inside the flex item.

You wrote:

In the fiddle provided below, you’ll see a number pad. All the rows contain 3 numbers except the bottom row. That row should have the ‘0’ be the width of 2 numbers, hence flex-grow: 2 and the ‘:’ be the size of 1 number, hence flex-grow: 1. Am I missing something here?

Yes. Your interpretation of the flex-grow property is incorrect. flex-grow is not intended for defining the size of a flex item. Its job is to distribute free space in the flex container among items.

By applying flex-grow: 1 to a group of flex items, you are telling them to distribute free space evenly among themselves. This is why, in your demo, rows 1, 2 and 3 have equally sized flex items.

When you apply flex-grow: 2, you are telling the flex item to consume twice as much free space as items with flex-grow: 1.

But where does the second 10px margin from the rows above factor into the layout of row 4?

enter image description here

The reason the alignment is off on row 4 is that row 4 has one less margin than the other rows, meaning that row 4 has 10px more free space than the other rows.

You’ll notice that if you remove the margin rule you get your desired alignment.

.numbers {
    display: flex;
    flex-direction: column;
}

.row {
    display: flex;
    flex-direction: row;
    flex-grow: 1;
    justify-content: space-between;
}

.button {
    display: flex;
    flex-grow: 1;
    justify-content: center;
    align-items: center;
    /* margin: 5px; */
    border-radius: 5px;
    border: 1px solid gray;
    background: rgba(255, 255, 255, 0.2);
    cursor: pointer;
}

.button#number0 {
    flex-grow: 2;
}

.button#colon {
    flex-grow: 1;
}
<div class="numbers">
    <div class="row">
        <div class="button number" id="number1">1</div>
        <div class="button number" id="number2">2</div>
        <div class="button number" id="number3">3</div>
    </div>
    <div class="row">
        <div class="button number" id="number4">4</div>
        <div class="button number" id="number5">5</div>
        <div class="button number" id="number6">6</div>
    </div>
    <div class="row">
        <div class="button number" id="number7">7</div>
        <div class="button number" id="number8">8</div>
        <div class="button number" id="number9">9</div>
    </div>
    <div class="row">
        <div class="button number" id="number0">0</div>
        <div class="button" id="colon">:</div>
    </div>
</div>

So what happens on row four to that second 10px margin?

It gets absorbed by the two flex items.

Here’s how flex-grow distributes the extra space on row four:

  • Flex item left (with content “0”) has flex-grow: 2. (.button#number0 in your code.)
  • Flex item right (with content “:”) has flex-grow: 1. (.button#colon in your code.)
  • The second inter-item margin, which appears only on rows with three flex items, is 10px wide. (The code says 5px around each item, but in
    CSS horizontal margins never
    collapse
    .
    Moreover, in flexbox, no margins
    collapse
    .)
  • The sum total of the flex-grow values is three. So let’s divide 10px by 3. Now we know that the proportion of 1 is 3.33px.
  • Hence, flex item left gets 6.66px of the extra space, and flex item right gets 3.33px.
  • Let’s say that flex item left had flex-grow: 3 instead. Then flex item left would get 7.5px, and flex item right would get 2.5px.

The last part of your question says:

The right side of the ‘0’ should be aligned with the 8, 5, and 2 above it. It’s a bit off.

Because flex-grow applies only to free space, and is heavily influenced by content and margins, it’s not the most secure way to size flex items.

Try flex-basis instead. Add this to your code:

.button    { flex-basis: 33.33%; }
#number0   { flex-basis: calc(66.67% + 10px); }
*          { box-sizing: border-box; }

.numbers {
    display: flex;
    flex-direction: column;
}

.row {
    display: flex;
    flex-direction: row;
    flex-grow: 1;
    justify-content: space-between;
}

.button {
    display: flex;
    flex-basis: 33.33%;
    justify-content: center;
    align-items: center;
    margin: 5px;
    border-radius: 5px;
    border: 1px solid gray;
    background: rgba(255, 255, 255, 0.2);
    cursor: pointer;
}

#number0   { flex-basis: calc(66.67% + 10px); }
*          { box-sizing: border-box; }
<div class="numbers">
    <div class="row">
        <div class="button number" id="number1">1</div>
        <div class="button number" id="number2">2</div>
        <div class="button number" id="number3">3</div>
    </div>
    <div class="row">
        <div class="button number" id="number4">4</div>
        <div class="button number" id="number5">5</div>
        <div class="button number" id="number6">6</div>
    </div>
    <div class="row">
        <div class="button number" id="number7">7</div>
        <div class="button number" id="number8">8</div>
        <div class="button number" id="number9">9</div>
    </div>
    <div class="row">
        <div class="button number" id="number0">0</div>
        <div class="button" id="colon">:</div>
    </div>
</div>

jsFiddle demo


References:


EXTRA: CSS GRID SOLUTION

With the advent of CSS Grid, the code for this entire layout can be greatly simplified.

.numbers {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(26%, 1fr));
  grid-gap: 10px;
}

#number0 {
  grid-column: span 2;
}


/* non-essential decorative styles */
.button {
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 5px;
  border: 1px solid gray;
}
<div class="numbers">
  <div class="button number" id="number1">1</div>
  <div class="button number" id="number2">2</div>
  <div class="button number" id="number3">3</div>
  <div class="button number" id="number4">4</div>
  <div class="button number" id="number5">5</div>
  <div class="button number" id="number6">6</div>
  <div class="button number" id="number7">7</div>
  <div class="button number" id="number8">8</div>
  <div class="button number" id="number9">9</div>
  <div class="button number" id="number0">0</div>
  <div class="button" id="colon">:</div>
</div>

Leave a Comment