CSS Sticky Sidebar

This is my reference for how to make a non-responsive layout with a content area and a sticky sidebar.

The HTML looks like this:

  <main>
    <h1>CSS Sticky Sidebar</h1>
    <div class="package-1">
      <div class="content-1">
        <!-- content goes here -->
      </div>

      <div class="sidebar-1">
        <ul>
          <li>Item 1</li>
          <li>Item 2</li>
          <li>Item 3</li>
          <li>Item 4</li>
          <li>Item 5</li>
          <li>Item 6</li>
          <li>Item 7</li>
          <li>Item 8</li>
          <li>Item 9</li>
          <li>Item 10</li>
        </ul>
      </div>
    </div>
  </main>

The CSS Looks like this:

:root {
  --sidebar-width: 14rem;
}

.package-1 {
  display: flex;
  align-items: flex-start;
  gap: 1.5rem;
}

.content-1 {
  flex: 1;
  min-width: 0;
}

.sidebar-1 {
  position: sticky;
  flex: 0 0 var(--sidebar-width);
  width: var(--sidebar-width);
  top: 1.5rem;
  max-height: calc(100vh - 3rem); 
  overflow-y: auto;
}

Details


Flexbox makes child elements the same height by default. That would mean the sticky sidebar would take up the same height as the content which would end up with a lot of empty space at the end and prevent the sticky feather from kicking in. Setting align-items to flex-start adjusts the behavior of child elements so they only take up as much vertical space as they need.

  align-items: flex-start;

The main content area is set up with flex: 1 so that it changes size dynamically based on how much space is available after accounting for the sidebar. The min-width: 0; line prevents issues with content that would otherwise overflow the content area.

.content-1 {
  flex: 1;
  min-width: 0;
}

The CSS for the sidebar breaks down like this:

  • Set the sidebar to be sticky in the viewport.

      position: sticky;
  • Give it a fixed width.

      flex: 0 0 var(--sidebar-width);
      width: var(--sidebar-width);
  • Use `top` to give it some padding when it hits the top of the page.

      top: 1.5rem;
  • Set the max hight of the sidebar to a little less than the viewport height and set the overflow so it scrolls internally if the content exceeds the height.

      max-height: calc(100vh - 3rem); 
      overflow-y: auto;

Outro

This isn't a responsive design. If you squish it onto a phone it'll just squish instead of reorienting itself. I'm fine with that. It's purpose is to use with tools designed for a full size screen.

As with lots of things, this got way easier to do as CSS continued to improve. We live in the future, and some parts of it are very nice.

-a

Endnotes

  • The styles above provide the core layout. There's a few ancillary styles that make it look nicer. They also add a lot of space to the sidebar items to give it a better shot of needing to scroll. The extra styles look like this:

body {
  font-family: system-ui, sans-serif;
  max-width: min(52rem, calc(100vw - 2rem));
  margin-inline: auto;
  background-color: var(--default-background-color);
  color: var(--default-base-color);
  font-size: var(--default-font-size);
}

.sidebar-1 {
  background: #555;
  padding: 1rem;
  border-radius: 8px;
  margin-top: 1.2rem;
}

.content-1 p { 
  margin-bottom: 1rem; 
}

.sidebar-1 ul { 
  padding: 0;
  list-style: none;
}
.sidebar-1 li { 
  min-height: 8rem;
  padding: 0.5rem 0; 
  border-bottom: 1px solid #d0d7d9; 
}