8

I'm trying to create a flexbox layout of fixed height which scrolls the inner content when the inner content is too large.

Additionally if the content doesn't cause scrolling I want to fix a div with buttons to the bottom of the container.

I have a layout which works perfectly in Firefox but in Chrome it causes the bottom button div to clip on top of the content when it's large enough to cause scrolling.

This is the layout rendering properly in Firefox:

Firefox proper rendering

This is how it renders improperly in Chrome:

Chrome improper rendering

Additionally, this is how it should behave if there's not enough inner content to cause scrolling:

Small content rendering

.container {
  height: 150px;
  width: 300px;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  border: 1px solid #000;
  padding: 4px;
}

.options {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}

.spacer {
  flex: 1 1 auto;
}

.buttons {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}
<div class="container">

  <div class="options">
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
  </div>

  <div class="spacer"></div>

  <div class="buttons">
    <input type="button" value="Cancel">
    <input type="button" value="Save">
  </div>

</div>

https://jsfiddle.net/tdghqyuu/1/

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
jesse.rohland
  • 113
  • 1
  • 5

3 Answers3

16

Solution

Add this to your code:

.container > * {
  flex-shrink: 0; 
}

Explanation

An initial setting on flex items, according to the flexbox specification, should be flex-shrink: 1. This means that flex items are permitted to shrink in order to avoid overflowing the container.

Chrome and IE11 appear to be adhering to this guideline.

Other browsers, such as Firefox, Edge and Safari, seem to have flex-shrink set to 0 by default.

On the other hand, this may have little or nothing to do with flex-shrink. The problem may relate to the default min-height and min-width settings on flex items. (See here: Why don't flex items shrink past content size?)

Or maybe it's a combination of both. It really depends on the browser.

The fact is, browser implementations vary. That's why you have to test.

Spec initial settings are not entirely reliable because

  1. browsers represent independent entities that can do whatever they want, and
  2. the use of "interventions".

Interventions

An intervention is when a user agent decides to deviate slightly from a standardized behavior in order to provide a greatly enhanced user experience.

In other words, a browser takes it upon itself to provide settings that it feels are best for its users, regardless of spec guidelines.

Chrome seems to do this a lot. Here are some examples:

One thing appears certain: If you disable flex-shrink, your layout then works across all browsers. Add this to your code:

.container > * {
  flex-shrink: 0; 
}

.container>* {
  flex-shrink: 0;
}

.container {
  height: 150px;
  width: 300px;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  border: 1px solid #000;
  padding: 4px;
}

.options {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}

.buttons {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  margin-top: 10px;
  /* new; for demo only */
}
<div class="container">

  <div class="options">
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
  </div>

  <div class="buttons">
    <input type="button" value="Cancel">
    <input type="button" value="Save">
  </div>

</div>

You may also want to consider moving the scrollbar to the options box only, then you've truly fixed the buttons to the bottom of the screen. Make this adjustment to your code:

.container {
  height: 150px;
  width: 300px;
  display: flex;
  flex-direction: column;
  /* overflow-y: auto; */
  border: 1px solid #000;
  padding: 4px;
}

.options {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  overflow-y: auto; /* NEW */
  flex: 1;          /* NEW; may be necessary for overflow to work in some browsers */

}

.container > * {
  flex-shrink: 0;
}

.container {
  height: 150px;
  width: 300px;
  display: flex;
  flex-direction: column;
  /* overflow-y: auto; */
  border: 1px solid #000;
  padding: 4px;
}

.options {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  overflow-y: auto; /* NEW */
  flex: 1;          /* NEW; may be necessary for overflow to work in some browsers */
}

.buttons {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  margin-top: 10px; /* new; for demo only */
}
<div class="container">

  <div class="options">
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
    <div class="option">
      <input type="checkbox"> Option
    </div>
  </div>

  <div class="buttons">
    <input type="button" value="Cancel">
    <input type="button" value="Save">
  </div>

</div>

Also, I removed the spacer element you had. It doesn't seem necessary when there are a multitude of clean ways to create space between items in a flex container.

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • "provide a greatly enhanced user experience" such as all of your page's elements being rendered on top of each other in a confusing, jumbled mess. Thanks Chrome! I expected this from IE, though. – Andrew Koster Dec 08 '18 at 16:47
1

.container doesn't need to be "flex", a min-height property for .options may be enough:

        .container {
            height: 150px;
            width: 300px;
            /*display: flex;*/
            flex-direction: column;
            overflow-y: auto;
            border: 1px solid #000;
            padding: 4px;
        }

        .options {
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
            min-height: 125px;
        }

        .options > div
        {
            display: inline-block;
        }

        .spacer {
            flex: 1 1 auto;
        }

        .buttons {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
        }
<div class="container">

        <div class="options">
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
            <div class="option">
                <input type="checkbox"> Option
            </div>
        </div>

        <div class="spacer"></div>

        <div class="buttons">
            <input type="button" value="Cancel">
            <input type="button" value="Save">
        </div>

    </div>
Bob Dust
  • 2,370
  • 1
  • 17
  • 13
  • min height worked mysteriously. It was working fine on chrome but on iphone (chrome) the siblings were overlapping. – Usman Iqbal Jul 04 '19 at 13:47
-2

This has actually nothing to do with flex. What you see is the options overflowing it's container (the <div class="options"> element). The element itself has the correct size, but the container itself is fixed height. If you add overflow: hidden or overflow: scroll to .options, it will render as you expect (although you might not want the scrollbar there).

giorgio
  • 10,111
  • 2
  • 28
  • 41
  • It has everything to do with flex. This is caused by Chrome's bad flex defaults, and it's fixed when you override it with a sane flex-shrink setting. – Andrew Koster Dec 08 '18 at 16:45