Search code examples
csscss-grid

Responsive CSS grid with scrollable cell overflows its parent container


I have a classic "top navigation bar - sidebar - main content" CSS grid layout which is also responsive and on smaller screens the sections are re-arranged vertically. The grid is styled to stretch out to the entire viewport. The main area is vertically scrollable if the content is longer than the viewport, whereas the top and side navigation bars are "pinned" to their positions and can't get smaller than their content in either direction.

To achieve the vertical scrolling in the main area, I've set the max-height property of the grid container to 100vh, and set the overflow-y property of the main element to auto. This max-height property, however, seems to have an adverse side-effect on the grid: the content can vertically overflow the grid container once the viewport's height becomes smaller than the minimum needed space to display the top navigation bar and sidebar (that can't shrink below their content).

This is what it looks like, for example, on a smaller screen, where the grid cells are stacked on top of each other:

Overflowing content on smaller screens

The yellow overlay on the picture above is the scrolled-out part, not visible in the browser window. The main area is at the bottom with zero height (plus the border) but this is okay, as the main element is vertically scrollable and the grid row is set to have 1fr height. The black border is for the entire grid container. As the example shows, the sidebar and the main area is outside of the container.

The same thing on a larger screen, where the sidebar is moved to the left, looks like this:

enter image description here

What I would like to have, however, is that the grid cells don't overlap with the grid container but they are fully contained. That is, on a smaller screen with vertically stacked cells, this:

enter image description here

And on larger screens, with the sidebar on the left:

enter image description here

Can this be achieved using some modified version of the grid structure and CSS given below in the code snippet? So basically I would like to have a scrollable main area with top navigation bar and sidebar which can't shrink vertically below their minimum required height, and a grid container that doesn't overlap with its content. I'd like to avoid anything explicitly sized to a specific value (i.e. I would not like to use something like height: 120px.

Note, that this is a follow-up question on my previous one but it's not the same because I now have the requirement of a scrollable main area.

* {
  box-sizing: border-box;
}

body {
  margin: 0;
}

ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

.bordered {
  border-style: solid;
  border-width: 5px;
}

/* grid layout */

.grid {
  display: grid;
  /* Default (small screen) layout. */
  /* Single column with 3 rows, the last one filling out the remaining space. */
  grid-template-rows: min-content min-content 1fr;
  max-height: 100vh;
  min-height: 100vh;
  min-width: min-content;
}

@media (min-width: 640px) {
  .grid {
    /* Larger screen layout. */
    /* 2 columns with 2 rows, the bottom right cell filling out the remaining space. */
    grid-template-columns: max-content 1fr;
    grid-template-rows: min-content 1fr;
  }

  nav {
    grid-column: span 2 / span 2;
  }
}

aside, nav {
  white-space: nowrap;
}

main {
  overflow-y: auto;
}
<html>

<body>
  <div class="grid bordered" style="border-color: black;">
    <nav class="bordered" style="border-color: green;">Top nav</nav>
    <aside class="bordered" style="border-color: red;">
      <ul>
        <li>Some example sidebar item #1</li>
        <li>Some example sidebar item #2</li>
        <li>Some example sidebar item #3</li>
        <li>Some example sidebar item #4</li>
        <li>Some example sidebar item #5</li>
      </ul>
    </aside>
    <main class="bordered" style="border-color: cyan;">
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
      <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
      <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
      <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </main>
  </div>
</body>

</html>


Solution

  • A colleague of mine suggested to use the overflow-y: auto property on the grid container itself. While its effect is not exactly the same as I was looking for, it's very similar and it does prevent the sidebar content from overflowing the container. The only minor difference is that when the available space becomes smaller than the necessary minimum to display the content of the grid, it's the grid container that gets a vertical scroll bar instead of the viewport.

    Below is the modified code snippet that demonstrates it.

    * {
      box-sizing: border-box;
    }
    
    body {
      margin: 0;
    }
    
    ul {
      list-style-type: none;
      margin: 0;
      padding: 0;
    }
    
    .bordered {
      border-style: solid;
      border-width: 5px;
    }
    
    /* grid layout */
    
    .grid {
      display: grid;
      /* Default (small screen) layout. */
      /* Single column with 3 rows, the last one filling out the remaining space. */
      grid-template-rows: min-content min-content 1fr;
      max-height: 100vh;
      min-height: 100vh;
      min-width: min-content;
      overflow-y: auto; /* <-- the only property added to the original CSS */
    }
    
    @media (min-width: 640px) {
      .grid {
        /* Larger screen layout. */
        /* 2 columns with 2 rows, the bottom right cell filling out the remaining space. */
        grid-template-columns: max-content 1fr;
        grid-template-rows: min-content 1fr;
      }
    
      nav {
        grid-column: span 2 / span 2;
      }
    }
    
    aside, nav {
      white-space: nowrap;
    }
    
    main {
      overflow-y: auto;
    }
    <html>
    
    <body>
      <div class="grid bordered" style="border-color: black;">
        <nav class="bordered" style="border-color: green;">Top nav</nav>
        <aside class="bordered" style="border-color: red;">
          <ul>
            <li>Some example sidebar item #1</li>
            <li>Some example sidebar item #2</li>
            <li>Some example sidebar item #3</li>
            <li>Some example sidebar item #4</li>
            <li>Some example sidebar item #5</li>
          </ul>
        </aside>
        <main class="bordered" style="border-color: cyan;">
          <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
          <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
          <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
          <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
        </main>
      </div>
    </body>
    
    </html>