javascripthtmlcssflexboxgrid

restrict child div height to parent container height


I want the graph div to have a height that stretches all the way to the end of it's parent (graph-container) container, but not extend past it. I have tried setting graph's height to both 100% and inherit, but both of them result in it extending past it's parent container's bottom edge (I don't want to use overflow: hidden). Is there a way to set the height property on graph so it automatically sets its height to the right value?

Current output:output

What I want: enter image description here

Current code:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-size: 20px;
}

.wrapper {
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
}

.graph-container {
  border: 3px solid black;
  height: 60vh;
  width: 70vw;
  margin-top: 10vh;
}

.graph {
  height: 100%;
  width: 100%;
  border: 3px solid red;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="wrapper">
      <div class="graph-container">
        <h1>Graph Container</h1>
        <div class="graph">Graph</div>
      </div>
    </div>
  </body>
</html>


Solution

  • The overflow you see happens because there is already an element taking up some space of the parent, so height: 100% plus the height of the other element will obviously exceed the parents height.

    There are different ways to solve the problem.
    I recommend one of these two ways:

    However, you can also solve them using these ways:

    CSS Flexbox

    The parent needs display: flex and flex-direction: column to lay out its children in a column.

    The child that should grow to take up the remaining space needs flex-grow: 1.

    .graph-container {
      display: flex;
      flex-direction: column;
    }
    .graph {
      flex-grow: 1;
    }
    
    /* Ignore; only for displaying the example */
    * {
      margin: 0;
      font-size: 24px;
    }
    html, body {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .graph-container, .graph {
      border: 3px solid;
      box-sizing: border-box;
    }
    .graph-container {
      border-color: black;
      height: 60vh;
      width: 70vw;
    }
    .graph {
      border-color: red;
    }
    <div class="graph-container">
      <h1>Graph Container</h1>
      <div class="graph">Graph</div>
    </div>

    CSS Grid

    The parent needs display: grid and grid-template-rows: auto 1fr.

    grid-template-rows: auto 1fr makes the second child take up the remaining space that is left over after the other children get their respective space that is defined without the fr-unit.
    Here, <h1> gets its space it requires, and all the remaining space is given to .graph.

    .graph-container {
      display: grid;
      grid-template-rows: auto 1fr;
    }
    
    /* Ignore; only for displaying the example */
    * {
      margin: 0;
      font-size: 24px;
    }
    html, body {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .graph-container, .graph {
      border: 3px solid black;
      box-sizing: border-box;
    }
    .graph-container {
      height: 60vh;
      width: 70vw;
    }
    .graph {
      border-color: red;
    }
    <div class="graph-container">
      <h1>Graph Container</h1>
      <div class="graph">Graph</div>
    </div>

    CSS calc()

    Relation of font-size to height

    As with the solution of sergey kuznetsov, you can guess that height is approximately equal to font-size.

    But the actual height depends on line-height, which depends on the current font.

    The problem, illustrated

    If line-height is just 1.05-times (or 5%) larger than font-size, the difference is negligible for small values (due to rounding to pixels) but noticable for larger values.

    Examples (where floor() cuts off the decimal places):

    This means small font-size values like 20px provide the expected result:
    calc(100% - 20px - 3px) (20px due to font-size, 3px due to border-width)

    h1 {
      font-size: 20px;
    }
    .graph {
      height: calc(100% - 20px - 3px);
    }
    
    /* Ignore; only for displaying the example */
    * {
      margin: 0;
      font-size: 24px;
    }
    html, body {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .graph-container, .graph {
      border: 3px solid black;
      box-sizing: border-box;
    }
    .graph-container {
      height: 60vh;
      width: 70vw;
    }
    .graph {
      border-color: red;
    }
    <div class="graph-container">
      <h1>Title</h1>
      <div class="graph">Graph</div>
    </div>

    But large font-size values like 60px result in visible inaccuracies due to wrongly assuming that font-size equals line-height:
    calc(100% - 60px - 3px) (60px due to font-size, 3px due to border-width)

    h1 {
      font-size: 60px;
    }
    .graph {
      height: calc(100% - 60px - 3px);
    }
    
    /* Ignore; only for displaying the example */
    * {
      margin: 0;
      font-size: 24px;
    }
    html, body {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .graph-container, .graph {
      border: 3px solid black;
      box-sizing: border-box;
    }
    .graph-container {
      height: 60vh;
      width: 70vw;
    }
    .graph {
      border-color: red;
    }
    <div class="graph-container">
      <h1>Title</h1>
      <div class="graph">Graph</div>
    </div>

    Instead of assumptions, you should use the intended properties to avoid such errors. Define the height property directly instead of expecting that font-size and line-height will equal it.

    Similarly, you can define one value to be used for multiple properties with custom properties:

    Using CSS custom properties

    You set the height of the second child to the remaining space with calc(100% - TITLE_HEIGHT) where TITLE_HEIGHT is the height of the other child, <h1>.

    We can use CSS Custom properties to set the height to reduce the amount of "magic numbers", which makes refactoring the code later on easier, and perhaps already easier to read.
    I used --title-height for the height.

    Custom properties are only accessible in the element itself and its children, which means that we need to define it in their parent .graph-container.

    Now we can use the value of --title-height by resolving it using var() like this:
    var(--title-height)

    .graph-container {
      --title-height: 26px;
    }
    h1 {
      height: var(--title-height);
    }
    .graph {
      height: calc(100% - var(--title-height));
    }
    
    /* Ignore; only for displaying the example */
    * {
      margin: 0;
      font-size: 24px;
    }
    html, body {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .graph-container, .graph {
      border: 3px solid black;
      box-sizing: border-box;
    }
    .graph-container {
      height: 60vh;
      width: 70vw;
    }
    .graph {
      border-color: red;
    }
    <div class="graph-container">
      <h1>Graph Container</h1>
      <div class="graph">Graph</div>
    </div>

    JavaScript

    When using JS, we need to set the parent's height as well as the height of the <h1>-element, and calculate the remaining height for .graph. The remaining height can be set as the .graph's height using Element.style.height.

    var container = document.querySelector('.graph-container');
    var title = document.querySelector('h1');
    var graph = document.querySelector('.graph');
    
    var remainingHeight = container.clientHeight - title.clientHeight;
    graph.style.height = remainingHeight + "px"; // Make sure it is of unit "px"
    /* Ignore; only for displaying the example */
    * {
      margin: 0;
      font-size: 24px;
    }
    html, body {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .graph-container, .graph {
      border: 3px solid black;
      box-sizing: border-box;
    }
    
    .graph-container {
      height: 60vh;
      width: 70vw;
    }
    .graph {
      border-color: red;
      display: block;
    }
    <div class="graph-container">
      <h1>Graph Container</h1>
      <div class="graph">Graph</div>
    </div>

    The problem with this approach is, that the height will be fixed once found. That will make .graph not responsive. To achieve that .graph is responsive, we need to observe if the parent was resized using a ResizeObserver.

    Every time the parent resizes, recalculate the size of .graph.

    var container = document.querySelector('.graph-container');
    var title = document.querySelector('h1');
    var graph = document.querySelector('.graph');
    
    new ResizeObserver(() => {
      graph.style.height = container.clientHeight - title.clientHeight + "px";
    }).observe(container);
    /* Ignore; only for displaying the example */
    * {
      margin: 0;
      font-size: 24px;
    }
    html, body {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .graph-container, .graph {
      border: 3px solid black;
      box-sizing: border-box;
    }
    
    .graph-container {
      height: 60vh;
      width: 70vw;
    }
    .graph {
      border-color: red;
      display: block;
    }
    <div class="graph-container">
      <h1>Graph Container</h1>
      <div class="graph">Graph</div>
    </div>