No Time Dad

A blog about web development written by a busy dad

Flexbox Two Column Layout with Fixed Sidenav - Part 2

I wasn’t planning for my post on building a flexbox two column layout with a fixed sidenav to be a multi part series. I kept mentioning adding a header and other elements in that post, so I thought it would only make sense to take that flexbox starter template a little further.

I’ve been on a css and html starter template kick lately. Mostly I think it’s fun and challenging to build different flexbox layouts. It also sharpens my skills and leads me into parts of css that I might not have used before.

I’ll be starting where I left off in the last post, using the html and css shown below. The primary focus of this post will be to add a header element. I’ll also adjust the width of the content section.

.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>

Adding a header

There are lots of different ways to add a header and lots of different behaviors they can each have. For this example, I want the header to be sticky and not move when any part of the main element is scrolled.

The main element is where the content for the page lives, so the most obvious place for me to put the header is above it. I’ll use the semantic header element for this, but I’ll also add a header selector to that element, which is personal preference.

The header selector will include the position property with the value sticky. This very well named value will keep the header “stuck” to the top, or wherever the element is. The position: sticky; definition won’t work how I expect it to work without some combination of the top, bottom, left, or right properties included in the header selector. These properties give the browser a starting point for where the element should stick. It’s common to see top: 0; for sticky headers since they’re often at the top of the page.

In the header selector I’ve also defined background-color: lightblue;. This property might not seems like it’s important, but it is. Without that property defined the header element would be transparent it will appear as though the content is pushing the header up when scrolling.

Below is the updated html and css for the header element.

...

.header {
  position: sticky;
  top: 0;
  /* Prevent transparent background-color! */
  background-color: lightblue;
}

...
<div class="container">
  <header>Header</header>
  <main class="main">
    ...
  </main>
</div>
Side note on display

The header element automatically spanned the entire width of the page. Why does that happen? I didn’t set any properties to explicitly tell it to do that, but it did it anyways. I think this is interesting. But, also possibly confusing to people new to html and css.

It happened because the elements have display: block; set by default as part of the normal document flow. With display: block;, the element will start on a new line and occupy the entire width that’s available.

display: block;
<div class="parent">
  <code>display: block;</code>
</div>
.parent {
  background-color: lightcoral;
}

The opposite side of display: block; is display: inline;, which is not set by default. With the inline value, elements do not get a new line and do not fill the available width.

display: inline;
<div class="parent">
  <code>display: block;</code>
</div>
.parent {
  display: inline;
  background-color: lightgreen;
}
Adjusting the width

Leaving the header for a moment, I thought it would be nice if the main element did not span the entire width of the page. Similar to this blog post page, in fact, it would be nice if there was space on either side of the content.

As with most things in css, this can be achieved a few different ways. I think one of the more straight-forward ways is by defining a max-width property on the main selector. I can then use the margin-left: auto; and margin-right: auto; trick to center the section on the screen.

I like it when the header text aligns with the content in some way, so I’d like to have the same max-width property applied to the header. But, this presents a small problem.

If I apply max-width and the auto margins directly to the header selector, the header will no longer span the entire width of the page. Instead, it’ll be exactly as wide as the max-width value. To get around this problem and preserve the full width header, I’ll add a new child div to the header element and apply the max-width and auto margins to that element instead.

The complete html and css will look as follows:

<div class="container">
  <header class="header">
    <div class="header__content">Header</div>
  </header>
  <main class="main">
    <div class="sidebar">
      <ul>
        <li>item</li>
        ...
      </ul>
    </div>
    <div class="content">
      <ul>
        <li>content</li>
        ... 
      </ul>
    </div>
  </main>
</div>
.container {
  --sidebar-width: 150px;
  /* Add a new css custom property so the width can be re-used */
  --shared-max-width: 800px;
}

.header {
  position: sticky;
  top: 0;
  background-color: lightblue;
}

/* New css selector to ensure the parent header is full width */
.header__content {
  /* Match the header spacing with the content spacing */
  max-width: var(--shared-max-width);
  margin-left: auto;
  margin-right: auto;
}

.main {
  display: flex;
  /* Add some white space on either side of the content */
  max-width: var(--shared-max-width);
  margin-left: auto;
  margin-right: auto;
}

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

.content {
  margin-left: var(--sidebar-width);
  flex-grow: 1;
  background-color: lightgray;
}

Conclusion

Something tells me this isn’t the end for this template. I have a few more ideas on how I can improve it. The main one that jumps to mind is responsiveness. It’s currently not responsive. More on that to come, I guess.

There wasn’t a huge amount of work done here in part 2, but it was fun exploring how various properties work and eventually landing on a working solution. I think this flexbox starter template is really taking shape.

View the full demo for part two.