No Time Dad

A blog about web development written by a busy dad

Example Responsive Tailwind Side Navigation

You’d be surprised how many side navigations I’ve built recently. It feels like hundreds. Some more complicated than others. Some responsive, and some not. Whatever variety you can think of I’ve probably tried to make it in the last few months.

Why go on this side navigation bender? Well, it’s fun. And sometimes I see side navigation designs that are clever and I want to try and replicate. I also think it’s fun to try and make side navigations as simple as possible while still being almost production ready. That’s an interesting challenge in itself.

Which is what leads to me to this responsive Tailwind side navigation. The goal was to keep it simple but still have it look and feel nice. Which I feel like could also be the motto of Tailwindcss.

See below for how the tailwind side navigation will look on larger and smaller screens, as well as the full code. Full side navigation demo. Trying changing your screen size to see how it responds.

Larger screens (md:): nav-wide

Smaller screens: nav-narrow

// package.json
...
"react": "^17.0.2",
"tailwindcss": "^2.1.2",
...
import React from 'react';

const Index = () => (
  <div className="flex">
    {/* Side navigation section */}
    <div className="bg-gray-900 h-screen">
      <a href="#home" className="flex items-center p-2 my-2 text-gray-100">
        <FireSvg />
        <div className="hidden text-2xl font-semibold md:block md:ml-2">bored.io</div>
      </a>
      <nav>
        <ul>
          <NavLink
            styles="border-l-4 border-green-500 bg-gray-800"
            displayText="Dashboard"
            hrefLink="#dashboard"
            svg={<ChartSvg />} />
          <NavLink
            svg={<IntegrationSvg />}
            displayText="Integrations"
            hrefLink="#integrations" />
          <NavLink
            svg={<AnalyticsSvg />}
            displayText="Analytics"
            hrefLink="#analytics" />
        </ul>
      </nav>
    </div>
    {/* Content section */}
    <div className="flex-grow bg-gray-200"></div>
  </div>
);

const NavLink = ({ styles, svg, displayText, hrefLink }) => (
  <li className={`hover:bg-gray-800 ${styles || ''}`}>
    <a href={hrefLink} className="flex items-center p-2 text-gray-100">
      {svg}
      <div className="hidden font-semibold md:block md:ml-2">
        {displayText}
      </div>
    </a>
  </li>
);

const FireSvg = () => (
  <svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" viewBox="0 0 20 20" fill="currentColor">
    <path fillRule="evenodd" d="M12.395 2.553a1 1 0 00-1.45-.385c-.345.23-.614.558-.822.88-.214.33-.403.713-.57 1.116-.334.804-.614 1.768-.84 2.734a31.365 31.365 0 00-.613 3.58 2.64 2.64 0 01-.945-1.067c-.328-.68-.398-1.534-.398-2.654A1 1 0 005.05 6.05 6.981 6.981 0 003 11a7 7 0 1011.95-4.95c-.592-.591-.98-.985-1.348-1.467-.363-.476-.724-1.063-1.207-2.03zM12.12 15.12A3 3 0 017 13s.879.5 2.5.5c0-1 .5-4 1.25-4.5.5 1 .786 1.293 1.371 1.879A2.99 2.99 0 0113 13a2.99 2.99 0 01-.879 2.121z" clipRule="evenodd" />
  </svg>
);

const ChartSvg = () => (
  <svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" 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>
);

const IntegrationSvg = () => (
  <svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" viewBox="0 0 20 20" fill="currentColor">
    <path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM14 11a1 1 0 011 1v1h1a1 1 0 110 2h-1v1a1 1 0 11-2 0v-1h-1a1 1 0 110-2h1v-1a1 1 0 011-1z" />
  </svg>
);

const AnalyticsSvg = () => (
  <svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" 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>
);

export default Index;

The Index component shown above is technically an entire page that contains side navigation and content sections. Both of these sections sit inside of a flexbox container. This allows the content section to take up any remaining space on the page after the side navigation via tailwind’s flex-grow utility class which is equivalent to flex-grow: 1 using vanilla css.

Focusing on the side navigation, it’s composed of a header section and a nav links section. The header section contains an app icon and a large text app name. Which is standard for most web apps. I initialized a flexbox container for the header because it’s the most convenient way to align the icon and text. On smaller screens the app text should be hidden, which is why I apply Tailwind’s md:block utility class in addition to hidden, which is the mobile first default style applied.

The next section in this tailwind side navigation is the nav element which contains the links. The links sit inside of an unorder list <ul> element and are each list item <li> elements. Using li elements helps ensure that the link spans the entire width of the side navigation. This might just be personal preference, but for some reason I like it when the link spans the full width instead of having a gap at each end.

Similar to the header, each nav items uses flexbox for alignment as well as some spacing. I originally wrote out each indiviual nav item, but I quickly realized it’d be easier if I made a generic nav link component and passed some props to it.

const NavLink = ({ styles, svg, displayText, hrefLink }) => (
  <li className={`hover:bg-gray-800 ${styles || ''}`}>
    <a href={hrefLink} className="flex items-center p-2 text-gray-100">
      {svg}
      <div className="hidden font-semibold md:block md:ml-2">
        {displayText}
      </div>
    </a>
  </li>
);

The NavLink component is bordering on too many props in my opinion, but it’s fine for now. The styles prop is technically optional and is meant to apply “active” link styles as needed. This is what I’m using to turn the Dashboard link into the active link, adding a border and changing the background color slightly to indicate it’s currently selected.

This tailwind side navigation is only slightly above bare bones, but still a good starting point. It doesn’t use React router or similar to handle the route changes, but I think it’s set up nicely to integrate with any routing solution. Tailwindcss once again made this a breeze for me. The icons are from heroicons.