No Time Dad

A blog about web development written by a busy dad

Flexbox Two Column Layout with Fixed Sidenav

The basic idea behind this layout is that there are two columns on the page. The first column is the sidebar and the second column is the content. The goal is that the sidebar column does not scroll with the content column. It should be fixed in it’s position.

This concept and implementation can be intimidating to newcomers to css. Actually, even those who’re experienced with css can struggle with this layout. I probably wrote five different versions of this template before I landed on one that I liked and that actually worked how I expected.

The frustrating part about working on a layout is that when I go looking for help or examples online, I’m often sent down a rabbit hole. This usually happens because most of the answers you’ll find on stackoverflow are meant for someone elses specific problem that isn’t exactly like my problem. So, then what I end up doing is jamming their unique solution into my problem and I end up with more a of mess than when I started.

Taking a step back is usually the answer for me. If I have the time, I completely delete all of the code I’ve written and start over. Or, check the existing code into a local git branch. I also like to strip the problem down to its most simplest form, and remove any extra features that are making things more complicated.

As an example, I kept trying to add a header to this layout template and it was making things more complicated. I’ll eventually want a header on this template. Adjusting for the space of the header relative to the rest of the body is important, but my main focus was achieving the goal of a two column layout using flexbox where the columns scroll independent of each other. So, I removed the header.

I guess I haven’t learned my lesson yet, because I always try to implement too many features up front when working on a new layout or element. Stripping designs down to their simplest form is a skill that I’m still working on developing.

Building the layout

On to the actual flexbox two column layout. The page itself will consist of a parent div with a child main element. In this case, main represents the main part of the page so using the semantic html tag makes sense.

As I alluded to earlier, at some point in the future I might want to add a header to this layout so having the page content elements inside of main makes the most sense to me. I can just stack the header element on top of main later without having to worry about how the styles inside of main might impact it.

Below is the basic html structure of the element. I should note that I’m using a selector called “main” inside of the main element. This is redundant, I know, but I don’t like applying styles directly to elements. It’s just personal preference.

<div class="container">
  <main class="main">
    <div class="sidebar">
      ...
    </div>
    <div class="content">
      ...
    </div>
  </main>
</div>
Container
.container {
  --sidebar-width: 150px;
}

The container div element is almost useless in this template, but it might serve more of a purpose in the future. For now, it’s job is to define a css custom property called --sidebar-width. This property is used in several places later to help with spacing.

In the future, if I added a header, I might have another css custom property to define the height of header. I could then use this property in a few different places if I ended up having extra scroll space. I try very hard to avoid these kinds of problems but sometimes I end up with something like height: calc(100vh - var(--header-height)); in certain places.

Main

Is it cliche if I say that the main element is where the magic happens? The answer is yes, but I’ve said it anyways. This element is where the sidebar and content div elements are defined. It’s also where the flexbox container that holds both of those elements is created.

Flexbox is doing a few important things here. The first is that it’s aligning the two child elements in a row. The second is that it’ll (later) allow the content div element to fill all of the available space on the page.

...

.main {
  display: flex;
}
Sidebar

There are a lot of important properties in the sidebar selector. Maybe the most important is postition: fixed;, which removes the sidebar element from the normal document flow and locks it into position based on the values of any top, bottom, left, and right properties.

Now that I have the position set to fixed, I need to tell the browser what do when the content in the sidebar div element is larger than the element itself. This is where overflow-y comes in. It tells the browser what to do on the y-axis (the vertical axis) in this scenario. In this two column starter template I’ve set the value of overflow-y to auto, which will have the browser show the scrollbar if the content is larger than the parent div and hide the scrollbar when it’s not.

The next important property in the sidebar selector is the height property. This property is so important, in fact, that if it’s not there the scrollbar won’t show up at all. It gives the browser a frame of reference for when to show the scrollbar.

The last property in this selector that needs to be mentioned is the the width property. In general, I’m not a huge fan of setting widths of heights on elements. Especially for sidebars and headers. I like to let the content inside the element determine the width or height. But, because this layout uses position: fixed to ensure it doesn’t move, it’s necessary to set the width so it can be offset in the content selector later.

Without the width offset, the content section will be overlapped by the sidebar. This happens because the fixed positioning removes the sidebar from the document flow, so the browser thinks there is free space where the sidebar actually is. And since I’m using the width value in two places now, I went ahead and created a css custom property in the container div element so it can be shared between the two child elements.

.container {
  --sidebar-width: 150px;
}

...

.sidebar {
  position: fixed;
  overflow-y: auto;
  height: 100%;
  width: var(--sidebar-width);
}
Content

The content selector isn’t quite as complicated as the sidebar selector. But, it does have a coupple interesting things happening. The first is that it uses margin-left with the css custom property I defined, --sidebar-width. This pushes the content div element to the right so it doesn’t overlap the sidebar element.

The content selector then uses flex-grow to fill the remaining space on the page. This works because both the sidebar and content sections are child elements in a flexbox container.

Conclusion

This flexbox two column starter template was an interesting challenge. I tried to keep the code as concise as possible. Which in itself proved to be the most challenging part. It was definitely good practice, though. And now I have a great flexbox starter template to work from in the future. I think this template could be used to build an awesome documentation page.

...
.content {
  margin-left: var(--sidebar-width);
  flex-grow: 1;
}
Final code & demo

Full css flexbox two column starter template demo

.container {
  --sidebar-width: 150px;
}

.main {
  display: flex;
}

.sidebar {
  position: fixed;
  overflow-y: auto;
  height: 100%;
  width: var(--sidebar-width);
}

.content {
  margin-left: var(--sidebar-width);
  flex-grow: 1;
}
<div class="container">
  <main class="main">
    <div class="sidebar">
      <ul>
        <li>item</li>
        ...
      </ul>
    </div>
    <div class="content">
      <ul>
        <li>content</li>
        ... 
      </ul>
    </div>
  </main>
</div>