Flex parent won't shrink past child content, even though child has `0` computed width

Issue

Please consider the following example:

.flexer {
  display: inline-flex;
  border: 1px solid red;
  min-width: 0;
}
.extra {
  flex: 0 1 0%; /* flex-grow: 0; flex-shrink: 1; flex-basis: 0%; */
  min-width: 0;
  transition: flex-grow .3s cubic-bezier(.4,0,.2,1);
  overflow: hidden;
}
.flexer:hover .extra {
  flex-grow: 1
}
<div class="flexer">
  <div>
     test
  </div>
  <div class="extra">
    extra
  </div>
</div>

<hr>
<div class="flexer">
  <div>
     test
  </div>
</div> - How it should look, when not hovered
<br>
<div class="flexer">
  <div>
     test
  </div>
  <div>
    extra
  </div>
</div> - How it should look, when hovered
<br><br>
The red box should animate smoothly and precisely between the two widths (without delay).

I have trouble understanding why .flexer (the parent) doesn’t shrink when not hovered (e.g: the red box still remains full, instead of shrinking around test).

From this q/a I understand adding min-width: 0 to the child should allow the parent to shrink. I’ve added it to both child and parent, to no avail.

Note 1: I’m more interested in understanding the mechanics and why this happens than finding an alternative solution (javascript, absolute positioning, etc…).
I’d like to use flexbox and I’d like to animate flex-grow – or any other animatable flex prop – for this case, if at all possible.

Note 2: the markup is irrelevant (I’m open to changing it – e.g: adding a wrapper to any of the children, if that will make my example work).

Note 3: for a clearer understanding of the desired output, see the JS based answer I added after I realised this is not possible using only CSS.

Thanks for looking into this.

Solution

From all the answers so far, I understand there’s no clean CSS way to reduce the size of the parent based on computed widths of all children, while the children are being animated, using flexbox.

Technically, this means flexbox props (-shrink, -basis, -grow) can’t be used to animate here and (max-)width should be used instead. I must admit, I still find it hard to believe. I would have thought flexbox got this covered.

The problem with animating max-width or width is you can’t animate from 0 to auto without JavaScript.

Which leads to the following solution:

[...document.querySelectorAll('.flexer')].forEach(el => {
  const item = el.querySelector('.extra');
  if (item) {
    el.onmouseenter = () => {
      item.style.maxWidth = item.scrollWidth + 'px'
    }
    el.onmouseleave = () => {
      item.style.maxWidth = '0'
    }
  }
})
.flexer {
  display: inline-flex;
  border: 1px solid red;
}
.extra {
  transition: max-width .42s cubic-bezier(.4, 0, .2, 1);
  overflow: hidden;
  max-width: 0;
  white-space: nowrap;
}
<div class="flexer">
  <div>
    test
  </div>
  <div class="extra">
    &nbsp;extra
  </div>
</div>

<div class="flexer">
  <div>
    test
  </div>
  <div class="extra">
    &nbsp;I have a lot more content...
  </div>
</div>

Needless to point out, if anyone has a pure CSS solution, which animates perfectly from 0 to the appropriate width for each item, I’d be happy to mark their answer as accepted.

So far, none of the provided answers has the desired output, except this one, which uses JavaScript.

Answered By – a.h.g.

Answer Checked By – Clifford M. (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.