cssflexbox

Why is percentage height ignored when nested within multiple flex boxes with aligned items?


This is a very contrived example to distill the problem I'm observing. I know I can work around it, but I still want to understand why this doesn't work the way I expect it to. I'd basically like to know: Is it a bug? Or is it behaving as it's designed? If so, what are the specs?

Problem summary: Percentage height or max-height is undesirably ignored on an element that is nested within 2 (maybe more) flex boxes that are flex-direction: row, and align-items is anything but the default stretch (flex-end in the example).

Setup

The design has category boxes laid out in a row, each with item boxes, also laid out in rows. I want each category and item to size to its contents, limit its height to its parent, and align to the bottom. If there is overflow, I want the item to scroll its contents.

Here's a Fiddle: https://jsfiddle.net/72Lf6nas/

HTML:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="src/style.css" />
  </head>
  <body>
    <div class="container">
      <div class="category">
        <div class="item">
          <h2>Item 1</h2>
          <p>Small content in section 1</p>
        </div>
      </div>
      <div class="category">
        <div class="item">
          <h2>Item 2</h2>

          <p>Large content in section 2. This section should scroll.</p>
          <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
            ultrices mi sit amet laoreet pulvinar. Nam vel pulvinar ligula, sit
            amet dictum ligula. Maecenas tincidunt, dui vel sollicitudin
            euismod, purus ipsum tincidunt mauris, nec tincidunt dolor odio at
            turpis. Aenean non lacus ac purus rhoncus ultrices. Fusce ultrices
            iaculis tellus, et venenatis neque ullamcorper tincidunt. Curabitur
            non urna vitae odio hendrerit sagittis. Ut vitae accumsan arcu.
          </p>
        </div>
        <div class="item">
          <h2>Item 3</h2>
          Small content in section 2.
        </div>
      </div>
    </div>
  </body>
</html>

CSS:

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  height: 100vh;
}

.container {
  height: 100%;
  display: flex;
  /* Comment this out here or in .category to make .item's max-height work. */
  align-items: flex-end;
  overflow: hidden;
  
  gap: 5px;
  padding: 5px;
  border: 3px solid red;
}

.category {
  display: flex;
  flex: 1;
  /* Comment this out here or in .container to make .item's max-height work. */
  align-items: flex-end;
  max-height: 100%;
  overflow: hidden;

  border: 3px solid green;
  gap: 5px;
  padding: 5px;
}

.item {
  position: relative;
  
  width: 200px;
  /* PROBLEM: Not honored when both .container and .category have align-item specified */
  max-height: 100%;
  overflow-y: auto;

  border: 3px solid blue;
  padding: 10px;
}

What I expect: Screenshot - Item box that would overflow scrolls instead

What happens: Screenshot - item max-height not obeyed, and overflowed item contents don't scroll

(Sorry, I'm new, not allowed to embed images yet!)

Observations

The item box does not obey max-height: 100% and overflows, despite its parent category displaying obvious boundaries and implying presence of reference height. Setting item's height to a percentage value also does nothing, but it does respond if either height or max-height being set to a pixel value. Responding to absolute values implying only percentage value is ignored.

I thought maybe flex-end was somehow removing the height reference needed to calculate relative height, but keeping align-items: flex-end on the direct ancestor of item (category) while removing it from container will make it heed item's max-height correctly. It only borks when item's parent and grandparent both have align-items: flex-end specified. Even so, there should still be a derived reference height starting from body's 100vh, so it's a surprise that the double-nesting would cause this behavior.

Appreciate any help in understanding this!


Solution

  • The concept here is that of a definite height. A percentage height only works if the value of which it's a percentage is definite.

    The height of the viewport is definite. It's the height of the browser window as determined by the user.

    The height of the body element is given is viewport units, so its height is also definite.

    The height of the .container element is a percentage of the body element's height which is definite, so its height too is definite.

    The height of the .category elements is not given. Its align-self value is flex-end (adopted from the align-items value of its parent) which is insufficient to make the elements' heights definite.

    But if the align-self value it adopts (or specified directly) is "stretch" then that is sufficient to make the elements' heights definite. The Flexbox specification explicitly says so:

    If the flex item has align-self: stretch, redo layout for its contents, treating this used size as its definite cross size so that percentage-sized children can be resolved.

    This "redo the layout" means that it works both from parent to child and child to parent. If the .item element's align-self is stretch, then it will be the height of its .category parent which will, when its align-self is flex-end and thus height determined by its contents, in turn cause it to grow to the height of the .container element.