No Time Dad

A blog about web development written by a busy dad

Flexbox Sidenav Example

Intro

Building a nice sidenav can be tricky. And I guess everyone has their own definition of what “nice” is. So, I spent some time working on what I think is a great looking sidenav using flexbox that isn’t overly complicated (I hope). It can built on or dropped straight into a site to be used, but for this example I will not be adding the media queries to make it responsive. I’ll probably do a separate post on making the sidenav responsive since that is a little more involved.

For our purposes here, we are going to focus on 4 main elements:

  1. Sidenav container
  2. Sidenav header (logo and app name)
  3. Sidenav body (where the nav links go)
  4. Sidnav footer (this is optional, but I think it is a nice touch)

Here is a look at what we will be building. The focus will be on the sidenav itself, so the content portion is just there for reference. The content border is something I saw the folks at TailwindUI doing and I liked it, so I included it here too. You can leave it out if you wish.

Initial Setup and CSS Resets

I am going to use some CSS resets to try and normalize the layout between browsers and give us a blank slate to start from. I’ll include the resets below, but I won’t be including them again when I reference the css file so just be aware that they are there if your page starts to look different than mine.

I am also going to use the Roboto font. You’ll need to add a link to the font in your <head> tag and include the font-family: "Roboto", sans-serif; in your css file, preferably under an html or body declaration.

Lastly, I am going to be using a css color palette that I’ll be defining via custom properties in the :root declaration of my css file. I found this color palette via the awesome Tailwindink site where you can make your own palette for tailwindcss. Since Tailwindink is meant for creating tailwindcss palettes I had to modify the output to be css custom properties instead.

/* RESETS AND COLOR PALETTE */
*,
*::before,
*::after {
  box-sizing: border-box;
}
body {
  font-family: 'Roboto', sans-serif;
  padding: 0;
  margin: 0;
}
a {
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}
button {
  background-color: transparent;
  color: inherit;
  border-width: 0;
  padding: 0;
  cursor: pointer;
}
input::-moz-focus-inner {
  border: 0;
  padding: 0;
  margin: 0;
}
ul,
ol,
dd {
  margin: 0;
  padding: 0;
  list-style: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
}
p {
  margin: 0;
}

/* COLOR PALETTE */
:root {
  --denim-50: #f7fafb;
  --denim-100: #edf6fa;
  --denim-200: #d6e7f5;
  --denim-300: #b8d2f0;
  --denim-400: #88abe9;
  --denim-500: #5781e0;
  --denim-600: #3d5dca;
  --denim-700: #3348a4;
  --denim-800: #293876;
  --denim-900: #212d5a;
}

Sidenav Container

Our sidenav and content elements are going to sit inside of a page container, which we’ll set to display: flex; so both the sidenav and the content are in a row. We’ll use the page container to give our layout some structure, and as a way to modify the layout later if we want to add responsiveness. In the example below I include a placeholder box in the content section of the page.

We’ll use height: 100vh in the .sidenav class so the sidenav will fill the entire page height regardless of how much content is in the sidenav container. We’ll also add another display: flex; and a flex-direction: column; to the .sidenav class, which will make things easier for us when we add the sidenav footer because we’ll be able to use the magical margin-top: auto; to push the foot content to the bottom of the sidenav column and have it stay there.

Lastly, we’ll set the sidenav background-color using the color palette custom css properties we defined earlier in the :root definition.

An important thing to note about the sidenav is that we are not going to explicitly setting a width property. We want to let the content of the container determine the size. This will ensure that everything looks nice and even no matter how long a given nav link is (try to keep the text short though). It will also make things easier on us later when we add responsiveness. In the example below I just added the word “Hello” so you can see the sidenav. We’ll be adding the actual contents of the sidenav in the next section.

Below is our starting html and css, as well as an example of what the sidenav should be looking like so far.

.container {
  display: flex;
}

.sidenav {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: var(--denim-900);
}

.content {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  padding: 1.5rem;
}

.content__child {
  display: flex;
  flex-grow: 1;
  border: 10px dashed var(--denim-200);
  border-radius: 1rem;
}
<div class="container">
  <div class="sidenav">
    Hello
  </div>
  <div class="content">
    <div class="content__child"></div>
  </div>
</div>
Hello

Sidenav Header

For the header I’ll be using a random icon from the wonderful heroicons and the randomly selected “Simple.io” as our app name. Our goal for the sidenav content is to re-use as much of the css as possible, so the header is going to share very similar properties with the nav link items and vice versa. Any major changes between the two will just be modifiers to the main sidenav__item or sidenav__item_text. The first modifier we are going to use today is sidenav__item_text__main which will increase the font-size and font-weight of the app name to make it stand out a bit more.

The sidenav__item class starts off by setting the display: flex; so the icon and text are displayed in a row. We align the icon and the text with each other using align-items: center;. If you left the align-items center property out you’d notice that the text would sit slightly above the icon. The remaining css properties are primarily spacing related.

Another thing to look out for here is to be sure that the all of the icons in the sidenav are aligned vertically and with the same spacing all around. This will again help us with responsiveness later if we wanted to “collapse” the sidenav and just show the icons.

We’ll be adding the css below to our existing css file from above.

...
.sidenav__item {
  display: flex;
  align-items: center;
  margin: 1rem;
  padding: 0.5rem;
  border-radius: 0.5rem;
}

.sidenav__item_icon {
  padding-top: 2px;
  width: 2rem;
  height: 2rem;
  margin-right: 1rem;
  color: var(--denim-50);
}

.sidenav__item_text {
  margin-right: 2rem;
  color: var(--denim-50);
}

.sidenav__item_text__main {
  font-size: large;
  font-weight: 900;
}
<div class="container">
  <div class="sidenav">
    <a class="sidenav__item" href="#main">
      <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
      </svg>
      <div class="sidenav__item_text sidenav__item_text__main">
        Simple.io
      </div>
    </a>
  </div>
  <div class="content">
    <div class="content__child"></div>
  </div>
</div>

Sidenav Body

Now we can add the actual nav links to the sidenav body. These are going to reuse the same css classes used for the header, minus the sidenav__item_text__main modifier css class.

The nav links are going to need modifier classes of their own because we’ll want to adjust the spacing between the links a little bit to make them closer together, as well as add things like :hover and active styles to make the sidenav more user friendly. We’ll be adding three more css classes as modifiers to our css file, and appending those new classes to the nav link elements in our html file.

I have arbitrarily hardcoded the Dashboard page example below to be “active”, but you can set any of the links you like as active…or better yet let your web framework decide! For our purposes today, clicking on another link will not change the active style.

You might have noticed in the previous section that there was a border-radius: 0.5rem; property set on the sidenav__item that seemingly did nothing. Once the background-color is changed you can see that that property does, in fact, do something.

The appended modifiers will looking something like this:

<a class="sidenav__item sidenav__item__link sidenav__item__active" href="#users">
...
</a>

Just remember that modifiers are applied in the order that they are listed in the css file itself, not the order they are listed in the html. So be careful not to accidentally override your styles by having them declared out of order.

Below are the three new css classes that we’ll be adding to our existing css file, as well as the classes appended to our existing html, and an example of where we’re at so far. Hover over the links to see the background-color change, and hover over the header to see that the background-color does not change.

...
.sidenav__item__link {
  margin-left: 1rem;
  margin-right: 1rem;
  margin-top: 0.5rem;
  margin-bottom: 0.5rem;
}

.sidenav__item__link:hover {
  background-color: var(--denim-700);
}

.sidenav__item__active {
  background-color: var(--denim-700);
}
<div class="container">
  <div class="sidenav">
    <a class="sidenav__item" href="#main">
      <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
      </svg>
      <div class="sidenav__item_text sidenav__item_text__main">
        Simple.io
      </div>
    </a>
    <a class="sidenav__item sidenav__item__link styles.sidenav__item__active" href="#dashboard">
      <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
        <path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z" />
      </svg>
      <div class="sidenav__item_text">
        Dashboard
      </div>
    </a>

    <a class="sidenav__item sidenav__item__link" href="#users">
      <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
        <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
      </svg>
      <div class="sidenav__item_text">
        Users
      </div>
    </a>

    <a class="sidenav__item sidenav__item__link href="#analytics">
      <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
        <path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z" />
        <path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z" />
      </svg>
      <div class="sidenav__item_text">
        Analytics
      </div>
    </a>
  </div>
  <div class="content">
    <div class="content__child"></div>
  </div>
</div>

Sidenav Footer

The last thing we’ll want to do is add a footer, which is a useful place to put a logout link or a help link. You could also put the authenticated user’s profile link there too.

The footer container will have a top-border to differentiate it a little bit from the rest of the nav. The link itself will be the same one we used in for the rest of the nav links.

We’ll create a new css class called sidenav__footer which will contain the magical margin-top: auto; that I mentioned earlier to push and keep the footer at the bottom. We’ll also add the border-top property here. Aside from that we’ll just plug in the same sidenav__item element as used above.

Below is the new css class that we’ll be adding to our existing css file, as well as the new html element for the footer. The full code will be shown after this section.

...
.sidenav__footer {
  margin-top: auto;
  border-top: 2px solid var(--denim-700);
}
...
<div class="sidenav__footer">
  <a class="sidenav__item sidenav__item__link" href="#help">
    <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
      <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
    </svg>
    <div class="sidenav__item_text">
      Help
    </div>
  </a>
</div>
...

Full Code and Thoughts

That is pretty much it for now. Obviously there is more that can be done with this sidenav, but I think it is an excellent jumping off point or something to drop in quickly if needed. I’ll be adding a post soon to work on making it responsive and maybe a few other features.

I really liked creating the color palette for this demo. This is something I hadn’t really tried before and got a taste for it when using tailwindcss a bit. Having a color palette is so helpful and really makes your work look a lot more professional.

Below is the full css and html element. The demo can be found here

:root {
  --denim-50: #f7fafb;
  --denim-100: #edf6fa;
  --denim-200: #d6e7f5;
  --denim-300: #b8d2f0;
  --denim-400: #88abe9;
  --denim-500: #5781e0;
  --denim-600: #3d5dca;
  --denim-700: #3348a4;
  --denim-800: #293876;
  --denim-900: #212d5a;
}

.container {
  display: flex;
}

.sidenav {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: var(--denim-900);
}

.content {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  padding: 1.5rem;
}

.content__child {
  display: flex;
  flex-grow: 1;
  border: 10px dashed var(--denim-200);
  border-radius: 1rem;
}

.sidenav__item {
  display: flex;
  align-items: center;
  margin: 1rem;
  padding: 0.5rem;
  border-radius: 0.5rem;
}

.sidenav__item_icon {
  padding-top: 2px;
  width: 2rem;
  height: 2rem;
  margin-right: 1rem;
  color: var(--denim-50);
}

.sidenav__item_text {
  margin-right: 2rem;
  color: var(--denim-50);
}

.sidenav__item_text__main {
  font-size: large;
  font-weight: 900;
}

.sidenav__item__link {
  margin-left: 1rem;
  margin-right: 1rem;
  margin-top: 0.5rem;
  margin-bottom: 0.5rem;
}

.sidenav__item__link:hover {
  background-color: var(--denim-700);
}

.sidenav__item__active {
  background-color: var(--denim-700);
}

.sidenav__footer {
  margin-top: auto;
  border-top: 2px solid var(--denim-700);
}
<div class="container">
  <div class="sidenav">
    <a class="sidenav__item" href="#main">
      <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
      </svg>
      <div class="sidenav__item_text sidenav__item_text__main">
        Simple.io
      </div>
    </a>
    <a class="sidenav__item sidenav__item__link styles.sidenav__item__active" href="#dashboard">
      <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
        <path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z" />
      </svg>
      <div class="sidenav__item_text">
        Dashboard
      </div>
    </a>

    <a class="sidenav__item sidenav__item__link" href="#users">
      <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
        <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
      </svg>
      <div class="sidenav__item_text">
        Users
      </div>
    </a>

    <a class="sidenav__item sidenav__item__link href="#analytics">
      <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
        <path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z" />
        <path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z" />
      </svg>
      <div class="sidenav__item_text">
        Analytics
      </div>
    </a>

    <div class="sidenav__footer">
      <a class="sidenav__item sidenav__item__link" href="#help">
        <svg class="sidenav__item_icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
          <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
        </svg>
        <div class="sidenav__item_text">
          Help
        </div>
      </a>
    </div>
  </div>
  <div class="content">
    <div class="content__child"></div>
  </div>
</div>