enyo
enyo

Reputation: 16696

How to get a flexbox not to use more space than the container?

I am building a modal, and want it to be centred in my page and grow organically but only until it reaches a certain width and height.

I center this modal (.modal) with flexbox, and it works fine.

Inside this modal I have another flexbox (.modal-content), with a body (.modal-content__body) and a footer (.modal-content__footer) element. I want the body to grow until the whole .modal reaches my max-height, at which point I want the modal-content__body to start scrolling vertically.

The HTML hierarchy is:

.modal-container // Full screen
  .modal // Centred box with max-width and max-height
    .modal-content // The content of the modal
      .modal-content__body // Potentially growing content
      .modal-content__footer

I can't get the .modal-content not to overflow. It simply ignores the parent's (.modal) height and grows to provide space for the content.

If I remove the additional div of the .modal-content and simply merge the .modal with the .modal-content it works as expected, but because I'm using a framework (Angular) I can't change the way the HTML elements are encapsulated.

(NOTE: If I set max-height: -webkit-fill-available; on the .modal-content it works, but this property is not well supported so I can't use it)

Here is a code demonstrating the issue:

/* Centers the child */
.modal-container {
  display: flex;
  align-items: center;
  justify-content: center;
}


/* The box in the middle, that should grow
   organically, but be restricted as well */
.modal,
.modal-content--merged-with-modal {
  width: 30rem;
  max-width: calc(50vw - 3rem);
  max-height: calc(100vh - 3rem);
  /* Irrelevant styling */
  background: rgba(255, 255, 255, 0.6);
  border-radius: 3px;
  line-height: 1.5rem;
}

.modal-content {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.modal-content__body {
  flex: 1;
  min-height: 0;
  overflow-y: scroll;
  /* Irrelevant styling */
  padding: 0.75rem;
  background: rgba(0, 0, 255, 0.1);
}

.modal-content__footer {
  /* Irrelevant styling */
  border-top: 1px solid silver;
  background: rgba(0, 255, 0, 0.1);
  padding: 0.75rem;
}


/* Irrelevant styling to present the problem */

html {
  font-family: arial, sans-serif;
  font-size: 16px;
}

main {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
}

section {
  flex: 1;
  background: rgba(255, 0, 0, 0.1);
}
<main>

  <section class="modal-container">
    <div class="modal-content modal-content--merged-with-modal">
      <div class="modal-content__body">
        <code>BODY // </code> srollable<br>
        <br> Container resizes as expected when<br> the container height is reduced.<br>
        <br> In this case, the modal is the same<br> element, as the flexbox handling<br> the body and the footer.<br> .
        <br> .
        <br> .
        <br> .
        <br> .
        <br> .
        <br>
      </div>
      <div class="modal-content__footer">
        <code>FOOTER // </code> Works
      </div>
    </div>
  </section>

  <section class="modal-container">
    <div class="modal">

      <div class="modal-content">
        <div class="modal-content__body">
          <code>BODY // </code>simply grows<br>
          <br> Container overflows when<br> the container height is reduced.<br>
          <br> In this case, the modal <strong>contains</strong><br> the flexbox, that handles the<br> body and the footer.<br> .
          <br> .
          <br> .
          <br> .
          <br> .
          <br> .
          <br>
        </div>
        <div class="modal-content__footer">
          <code>FOOTER // </code> Doesn't work
        </div>
      </div>

    </div>
  </section>

</main>

Upvotes: 3

Views: 3317

Answers (2)

Michael Benjamin
Michael Benjamin

Reputation: 371193

In order for the overflow property to work, it needs a fixed length limitation.

From MDN:

In order for overflow to have an effect, the block-level container must have either a set height (height or max-height) or white-space set to nowrap.

In your code, there is no height limitation on .modal-content__body (where the overflow is set):

.modal-content__body {
    flex: 1;
    min-height: 0;
    overflow-y: scroll;
}

Nor is there a height limitation on the parent:

.modal-content {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

flex-basis: 0 – the relevant component of flex: 1 in .modal-content__body – isn't enough to trigger the overflow condition. But look what happens if you switch to flex-basis: 100px

/* Centers the child */
.modal-container {
  display: flex;
  align-items: center;
  justify-content: center;
}
  
/* The box in the middle, that should grow
   organically, but be restricted as well */
.modal, .modal-content--merged-with-modal {
  width: 30rem;
  max-width: calc(50vw - 3rem);
  max-height: calc(100vh - 3rem);

  /* Irrelevant styling */
  background: rgba(255, 255, 255, 0.6);
  border-radius: 3px;
  line-height: 1.5rem;
}
  
  
.modal-content {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
  
.modal-content__body {
  flex: 1 0 100px; /* new for demo 1 */
  min-height: 0;
  overflow-y: scroll;

  /* Irrelevant styling */
  padding: 0.75rem;
  background: rgba(0, 0, 255, 0.1);
}
  
  
.modal-content__footer {
  /* Irrelevant styling */
  border-top: 1px solid silver;
  background: rgba(0, 255, 0, 0.1);
  padding: 0.75rem;
}
  
  
  
/* Irrelevant styling to present the problem */
html {
  font-family: arial, sans-serif;
  font-size: 16px;
}
main {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  
  display: flex;
}
section {
  flex: 1;
  background: rgba(255, 0, 0, 0.1);
}
<main>
  <section class="modal-container">
    <div class="modal">
      <div class="modal-content">
        <div class="modal-content__body">
          <code>BODY // </code>simply grows<br>
          <br> Container overflows when<br> the container height is reduced.<br>
          <br> In this case, the modal <strong>contains</strong><br> the flexbox, that handles the<br> body and the footer.<br> .
          <br> .
          <br> .
          <br> .
          <br> .
          <br> .
          <br>
        </div>
        <div class="modal-content__footer">
          <code>FOOTER // </code> Doesn't work
        </div>
      </div>
    </div>
  </section>
</main>

The scrolling works. A height limitation made the difference.

Here's a potential solution:

Since the height of .modal (the parent) and modal-content (the child) are the same, move the dimensions from the parent to the child. This brings the height limitation close enough to the content items to trigger the overflow.

Instead of this:

.modal {
    width: 30rem;
    max-width: calc(50vw - 3rem);
    max-height: calc(100vh - 3rem);
}

.modal-content {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

Try this:

.modal {    }

.modal-content {
    display: flex;
    flex-direction: column;
    min-height: 0;
    width: 30em;
    max-width: calc(50vw - 3rem);
    max-height: calc(100vh - 3rem);
}

/* Centers the child */
.modal-container {
  display: flex;
  align-items: center;
  justify-content: center;
}
  
/* The box in the middle, that should grow
   organically, but be restricted as well */
.modal, .modal-content--merged-with-modal {


  /* Irrelevant styling */
  background: rgba(255, 255, 255, 0.6);
  border-radius: 3px;
  line-height: 1.5rem;
}
  
  
.modal-content {
  display: flex;
  flex-direction: column;
  min-height: 0;   
     width: 30rem;
     max-width: calc(50vw - 3rem);
     max-height: calc(100vh - 3rem);
}
  
.modal-content__body {
  flex: 1;
  min-height: 0;
  overflow-y: scroll;

  /* Irrelevant styling */
  padding: 0.75rem;
  background: rgba(0, 0, 255, 0.1);
}
  
  
.modal-content__footer {
  /* Irrelevant styling */
  border-top: 1px solid silver;
  background: rgba(0, 255, 0, 0.1);
  padding: 0.75rem;
}
  
  
  
/* Irrelevant styling to present the problem */
html {
  font-family: arial, sans-serif;
  font-size: 16px;
}
main {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  
  display: flex;
}
section {
  flex: 1;
  background: rgba(255, 0, 0, 0.1);
}
<main>

  <section class="modal-container">
    <div class="modal">

      <div class="modal-content">
        <div class="modal-content__body">
          <code>BODY // </code>simply grows<br>
          <br>
          Container overflows when<br>
          the container height is reduced.<br>
          <br>
          In this case, the modal <strong>contains</strong><br>
          the flexbox, that handles the<br>
          body and the footer.<br>
          .<br>
          .<br>
          .<br>
          .<br>
          .<br>
          .<br>
        </div>
        <div class="modal-content__footer">
          <code>FOOTER // </code> Doesn't work
        </div>
      </div>

    </div>
  </section>

</main>

Upvotes: 1

Temani Afif
Temani Afif

Reputation: 272817

A simple fix is to make the .modal a flex container with column direction.

/* Centers the child */
.modal-container {
  display: flex;
  align-items: center;
  justify-content: center;
}
  
/* The box in the middle, that should grow
   organically, but be restricted as well */
.modal, .modal-content--merged-with-modal {
  width: 30rem;
  max-width: calc(50vw - 3rem);
  max-height: calc(100vh - 3rem);

  /* Irrelevant styling */
  background: rgba(255, 255, 255, 0.6);
  border-radius: 3px;
  line-height: 1.5rem;
}
  
  
.modal-content {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
  
.modal-content__body {
  flex: 1;
  min-height: 0;
  overflow-y: scroll;

  /* Irrelevant styling */
  padding: 0.75rem;
  background: rgba(0, 0, 255, 0.1);
}
  
  
.modal-content__footer {
  /* Irrelevant styling */
  border-top: 1px solid silver;
  background: rgba(0, 255, 0, 0.1);
  padding: 0.75rem;
}
.modal {
  display:flex;
  flex-direction:column;
}
  
  
/* Irrelevant styling to present the problem */
html {
  font-family: arial, sans-serif;
  font-size: 16px;
}
main {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  
  display: flex;
}
section {
  flex: 1;
  background: rgba(255, 0, 0, 0.1);
}
<main>

  <section class="modal-container">
    <div class="modal-content modal-content--merged-with-modal">
      <div class="modal-content__body">
        <code>BODY // </code> srollable<br>
        <br>
        Container resizes as expected when<br>
        the container height is reduced.<br>
        <br>
        In this case, the modal is the same<br>
        element, as the flexbox handling<br>
        the body and the footer.<br>
        .<br>
        .<br>
        .<br>
        .<br>
        .<br>
        .<br>
      </div>
      <div class="modal-content__footer">
        <code>FOOTER // </code> Works
      </div>
    </div>
  </section>

  <section class="modal-container">
    <div class="modal">

      <div class="modal-content">
        <div class="modal-content__body">
          <code>BODY // </code>simply grows<br>
          <br>
          Container overflows when<br>
          the container height is reduced.<br>
          <br>
          In this case, the modal <strong>contains</strong><br>
          the flexbox, that handles the<br>
          body and the footer.<br>
          .<br>
          .<br>
          .<br>
          .<br>
          .<br>
          .<br>
        </div>
        <div class="modal-content__footer">
          <code>FOOTER // </code> Doesn't work
        </div>
      </div>

    </div>
  </section>

</main>

Why?

Initially the .modal was a block element with a max-height set and the default overflow behavior (when there is an overflow) is visible and you are facing an overflow issue.

By making the .modal a flex container, you introduce the shrink effect as by default the flex item will have flex-shrink:1 so its height won't go beyond the height of the flex container (it will shrink to fit) and you will have the need result.

You may set flex-shrink:0 to .modal-content and you will see the same overflow issue as if there is no display:flex.

Upvotes: 3

Related Questions