html – Full page with nested flexbox and scrolling

I’m trying to do what I thought was a fairly standard sort of full-screen layout using flexbox, but it’s not behaving the way I want. My goals are:

  • Auto sizing of topbar/footer height and sidebar width (size to content, not fixed size)
  • Topbar/footer take full width and never scroll
  • Sidebar takes full height but if contents are larger can vertically scroll only
  • Content takes leftover width/height but if contents are larger can scroll both ways
  • Sidebar/Content scroll independently of each other (and hide scrollbars when not needed)
  • Main content area will usually appear as regular doc with scrollbar, but sometimes I want it to also have some nested sidebars with a remaining-height scrolling subpanel.
* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
}

.page {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.topbar {
  background-color: lightgreen;
}

.vcontainer {
  flex-grow: 1;
  display: flex;
  flex-direction: row;
  min-height: 0;
}

.sidebar {
  overflow-y: auto;
  background-color: yellow;
}

.content {
  flex-grow: 1;
  overflow: auto;
  background-color: lightblue;
}

.footer {
  background-color: pink;
}
<div class="page">
  <nav class="topbar">
    top bar
  </nav>
  <div class="vcontainer">
    <nav class="sidebar">
      side<br/>side<br/>side
    </nav>
    <main class="content">
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
      <p>some actual content</p>
    </main>
  </div>
  <footer class="footer">
    footer
  </footer>
</div>

The above works fine for the full doc scrolling variant (although the magic was adding the min-height to vcontainer; it didn’t work prior to that) — the blue area takes the remaining space and is scrollable if the content gets too long.

Without changing the outer styling (or at least not changing it in a way that breaks the above scenario), I want to be able to sometimes put in some content that essentially has an additional dynamic-height topbar and a scrolling subsection that doesn’t trigger the main content scrollbar.

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
}

.page {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.topbar {
  background-color: lightgreen;
}

.vcontainer {
  flex-grow: 1;
  display: flex;
  flex-direction: row;
  min-height: 0;
}

.sidebar {
  overflow-y: auto;
  background-color: yellow;
}

.content {
  flex-grow: 1;
  overflow: auto;
  background-color: lightblue;
}

.footer {
  background-color: pink;
}

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

.contentsub {
  margin: 1rem;
  overflow-y: scroll;
  flex-grow: 1;
  background-color: orange;
}
<div class="page">
  <nav class="topbar">
    top bar
  </nav>
  <div class="vcontainer">
    <nav class="sidebar">
      side<br/>side<br/>side
    </nav>
    <main class="content">
      <div class="contentcontainer">
        <div>
          some additional heading content
        </div>
        <div class="contentsub">
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
          <p>some actual content</p>
        </div>
        <div>
          some additional footer content
        </div>
      </div>
    </main>
  </div>
  <footer class="footer">
    footer
  </footer>
</div>

What I’m wanting is for the blue area to not have a scrollbar in this case, but instead for the orange area to be scrollable, surrounded by the extra header/footer and sized to fill the leftover space. Ideally, this should work even if contentsub isn’t a direct child of the contentcontainer but there’s more wrapping elements and borders etc before getting to the scrollable element. I would prefer a solution that’s “pure flexbox”, but if there’s a better way to do it then I’m all ears. I do want to avoid using javascript, however.

Leave a Comment