Create a simple sidebar with NextJS & Tailwind CSS in 30 minutes!

Create a simple sidebar with NextJS & Tailwind CSS in 30 minutes!

Introduction

Sometimes, it's tough to know where to start. It's something that a lot of developers especially newbies struggle with including me. That's why I'm writing this article so that you know where to start when building a simple navigation menu that's mobile responsive.

I think being able to style a navigation menu for mobile users is like being a superhero. You're helping people! So today, I'm going to help you become the next superhero in town.

For this project, we'll be using Next.js, Tailwind CSS & React Hooks, especially the useState hook. I'll link to the finished project at the end.

Here's a snapshot of what we're going to be building 👇🏾

Prerequisites

  1. Create-next-app

  2. Basics of React and React Hooks

  3. Basics of Tailwind CSS

  4. Git

Ok, so since you're still here, let's do this!

Getting Started

The first thing to do is to create a new Next.js application. You can do this with the help of create-next-app. So open up your favourite terminal and run the command below:

npx create-next-app@latest next-tailwind-sidebar

Please note that next-tailwind-sidebar is the name of the project. You can name this whatever you like but for this tutorial we are using next-tailwind-sidebar.

When you run the command above, you'll run into some questions like the ones below. This is all dependent on you however, for this project this is the configuration we are going with.

Once the installation is complete, go into the newly created folder by running the command.

cd next-tailwind-sidebar

Since this tutorial uses VSCode, there's a neat little shortcut to open your project in VS Code from the terminal. It goes a little something like this:

code .

And voila, your new project is set for you to get stuck in! Your project structure should resemble the image below.

Adding project dependencies & Tailwind

Now, it’s time to install our dependencies including Tailwind in our project. First install react-use, an npm package that provides a plethora of react hooks and react-icons, which is an icons library:

npm install react-use

npm install react-icons

Secondly, install Tailwind CSS. To install and set up Tailwind in your project, please follow the instructions for installing Tailwind CSS with Next.js.

Once you're done with the setup, the last thing to do is to get the server running with the command:

npm run dev

Awesome! So our project should now be ready for us to code! Off we go 🏃🏾‍♀️💨

In the default project, there are stylesheets and other files that come with the setup, go ahead and remove everything in the global.css file apart from the tailwind configurations you already included. You can also remove the Home.module.css file as we won’t be using it.

For this project, we will be creating a components folder that will house our Sidebar.js file which we can then use on the other pages to demonstrate the navigation and sidebar. Ideally, the navigation/sidebar component should be imported in for example your Layout file so that it’s available across all pages. However, we will be importing it on each page due to the simplicity of this project.

Also, go ahead and create 3-4 pages in your pages folder which we will use as our navigation links. For this project, I’ve created about.js, contact.js, product.js & services.js. As a start, these files will simply include the names of the files like so:

import React from 'react';

const Services = () => {
  return (
      <div className='text-lg font-bold'>
        <span>Services</span>
      </div>
  );
};

export default Services;

In the index.js file, go ahead and remove all the irrelevant imports and also the code inside the main tag. Your index file should now look like this.

import Head from 'next/head';

export default function Home() {
  return (
    <>
      <Head>
        <title>Next & Tailwind Sidebar</title>
        <meta name='description' content='Generated by create next app' />
        <meta name='viewport' content='width=device-width, initial-scale=1' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <main>
        {/* Sidebar Component */}
      </main>
    </>
  );
}

As a disclaimer, we will be building the desktop navbar first in this project and then implementing the mobile version which will be the sidebar. But it's a good idea to implement a mobile-first approach when creating your web projects because if we look at the statistics of mobile traffic over the years, it's been on a steady rise. So why not give them the attention they need?

Creating & Styling the Navbar

Without further ado, let’s start with creating and styling the navbar. First, let’s lay down the structure

  • displayMenu, a function that returns our menu for the navbar) in our Sidebar.js file.

  • menuItems, an array of our menu objects

You’ll notice that I’ve also started including the styling as well for the layout of our logo and menu links:

//Sidebar.js

import { Inter } from '@next/font/google';
import Image from 'next/image';
import Link from 'next/link';

import Logo from '../images/loop.svg';

const inter = Inter({ subsets: ['latin'] });

// Menu items
const menuItems = [
  {
    menu: 'About',
    link: '/about',
  },
  {
    menu: 'Product',
    link: '/product',
  },
  {
    menu: 'Services',
    link: '/services',
  },
  {
    menu: 'Contact Us',
    link: '/contact',
  },
];

export default function Home() {

const displayMenu = () => {
    return menuItems.map((item) => (
      <Link
        key={item.menu}
        href={item.link}
        className='pb-5 hover:text-yellow md:pb-0 md:pl-6'
      >
        {item.menu}
      </Link>
    ));
  };

  return (
    <>
      <Head>
        <title>Next & Tailwind Sidebar</title>
        <meta name='description' content='Generated by create next app' />
        <meta name='viewport' content='width=device-width, initial-scale=1' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <main>
                {/* Tailwind CSS classes added to navbar */}
        <div className='flex justify-between p-7 shadow-lg items-center'>
          {/* Logo */}
          <div>
            <Image src={Logo} alt='Logo' width={100} height={100} />
          </div>

          {/* Menu links */}
          <div className={inter.className}>
            <div className='text-red'>
              {displayMenu()}
            </div>
          </div>
        </div>
      </main>
    </>
  );
}

With the code above, your UI should look like this:

Quick side note, you'll notice a div with className={inter.className}. This is one of Next.js's many helpful features. You can read more on their website.

Now the desktop view looks good. Onto the main aspect which is the mobile view. To achieve this, we will utilise the help of a lovely hook in react-use called useMedia. Essentially, it allows us to capitalize on CSS media queries without all the extra work 😉. It goes a little something like this:

const isDesktop = useMedia('(min-width: 768px)', true);

What this piece of code does is that it returns a boolean value whenever the screen width is greater than 768px or not. This way, we can determine when our mobile view should take effect. The second parameter, which is the defaultState is optional but I find (not sure exactly yet as I found some odd behaviour when it's false) that it’s better to include it when using Nextjs. So in our case, we will set it to true.

With that in mind, we can add that to our code:

//Sidebar.js

import styles from '@/styles/Home.module.css';
import { Inter } from '@next/font/google';
import Head from 'next/head';
import Image from 'next/image';
import Link from 'next/link';
import { useMedia } from 'react-use';

import Logo from '../images/loop.svg';

const inter = Inter({ subsets: ['latin'] });

// Menu
const menuItems = [
  {
    menu: 'About',
    link: '/about',
  },
  {
    menu: 'Product',
    link: '/product',
  },
  {
    menu: 'Services',
    link: '/services',
  },
  {
    menu: 'Contact Us',
    link: '/contact',
  },
];

export default function Home() {

const displayMenu = () => {
    return menuItems.map((item) => (
      <Link
        key={item.menu}
        href={item.link}
        className='pb-5 hover:text-yellow md:pb-0 md:pl-6'
      >
        {item.menu}
      </Link>
    ));
  };  

{/* NEW LINE */}
const isDesktop = useMedia('(min-width: 768px)', true);

  return (
    <>
      <Head>
        <title>Next & Tailwind Sidebar</title>
        <meta name='description' content='Generated by create next app' />
        <meta name='viewport' content='width=device-width, initial-scale=1' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <main>
        <div className='flex justify-between p-2 shadow-lg items-center'>
          {/* Logo */}
          <div>
            <Image src={Logo} alt='Logo' width={100} height={100} />
          </div>

          {/* Menu links */}
          <div className={inter.className}>
            <div className='text-red'>
              {displayMenu()}
            </div>
          </div>
        </div>
      </main>
    </>
  );
}

We will be using the isDesktop variable to determine our mobile or desktop view. To do this, we'll use the ternary operator. So if it is desktop return the current view we have now and if it’s mobile return the mobile view which we will dive into.

Our code should look like this:

//Sidebar.js

import styles from '@/styles/Home.module.css';
import { Inter } from '@next/font/google';
import Head from 'next/head';
import Image from 'next/image';
import Link from 'next/link';
import { useMedia } from 'react-use';

import Logo from '../images/loop.svg';

const inter = Inter({ subsets: ['latin'] });

// Menu
const menuItems = [
  {
    menu: 'About',
    link: '/about',
  },
  {
    menu: 'Product',
    link: '/product',
  },
  {
    menu: 'Services',
    link: '/services',
  },
  {
    menu: 'Contact Us',
    link: '/contact',
  },
];

export default function Home() {

const displayMenu = () => {
    return menuItems.map((item) => (
      <Link
        key={item.menu}
        href={item.link}
        className='pb-5 hover:text-yellow md:pb-0 md:pl-6'
      >
        {item.menu}
      </Link>
    ));
  };  

const isDesktop = useMedia('(min-width: 768px)', true);

  return (
    <>
      <Head>
        <title>Next & Tailwind Sidebar</title>
        <meta name='description' content='Generated by create next app' />
        <meta name='viewport' content='width=device-width, initial-scale=1' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <main>
        <div className='flex justify-between p-2 shadow-lg items-center'>
          {/* Logo */}
          <div>
            <Image src={Logo} alt='Logo' width={100} height={100} />
          </div>

          {/* Menu links */}
          <div className={inter.className}>
            <div className='text-red'>
              {/* NEW LINE */}
              {isDesktop ? () : ()}
            </div>
          </div>
        </div>
      </main>
    </>
  );
}

All we’ve done is remove the displayMenu() call and include our ternary operator to check if we are in desktop view or not. Now, in the first bracket, which is what will be returned if the screen size is greater than 768px, we will include our initial desktop view which calls the displayMenu() function like so:

<div className='text-red'>
    {isDesktop ? (
      displayMenu()
    ) : ()}
</div>

In the second bracket, this is where our mobile view will exist. First, we will import our burger menu icon from react-icons:

import { FaBars } from 'react-icons/fa';

And then include a button with our newly imported burger menu:

<div className='text-red'>
    {isDesktop ? (
      displayMenu()
    ) : (
      <button>
        <FaBars />
      </button>
    )}
</div>

So when you save and put all of this together, your Sidebar.js file should look like this:

import styles from '@/styles/Home.module.css';
import { Inter } from '@next/font/google';
import Head from 'next/head';
import Image from 'next/image';
import Link from 'next/link';
import { FaBars } from 'react-icons/fa';
import { useMedia } from 'react-use';

import Logo from '../images/loop.svg';

const inter = Inter({ subsets: ['latin'] });

// Menu
const menuItems = [
  {
    menu: 'About',
    link: '/about',
  },
  {
    menu: 'Product',
    link: '/product',
  },
  {
    menu: 'Services',
    link: '/services',
  },
  {
    menu: 'Contact Us',
    link: '/contact',
  },
];

export default function Home() {

    const displayMenu = () => {
        return menuItems.map((item) => (
          <Link
            key={item.menu}
            href={item.link}
            className='pb-5 hover:text-yellow md:pb-0 md:pl-6'
          >
            {item.menu}
          </Link>
        ));
      };  

    const isDesktop = useMedia('(min-width: 768px)', true);

  return (
    <>
      <Head>
        <title>Next & Tailwind Sidebar</title>
        <meta name='description' content='Generated by create next app' />
        <meta name='viewport' content='width=device-width, initial-scale=1' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <main>
        <div className='flex justify-between p-2 shadow-lg items-center'>
          {/* Logo */}
          <div>
            <Image src={Logo} alt='Logo' width={100} height={100} />
          </div>

          {/* Menu links */}
          <div className={inter.className}>
            <div className='text-red'>
              {isDesktop ? (
                displayMenu()
              ) : (
                <button>
                  <FaBars />
                </button>
              )}
            </div>
          </div>
        </div>
      </main>
    </>
  );
}

Notice, when you drag your window and it gets below 768px, our burger icon is displayed as it should! Well done for getting this far…we’re almost there!

Let's add some simple styling to our burger menu button:

<button className='border-none bg-none'>
  <FaBars />
</button>

Now that we’ve got our mobile and desktop views sorted, we need to focus on the sidebar which pops out when our burger menu is clicked on. So we’ll start by creating the sidebar. This will be directly below our burger menu button.

<>
  <button className='border-none bg-none'>
    <FaBars />
  </button>
  <div>
    <div>
      {displayMenu()}
    </div>
  </div>
</>

The next thing we are going to do is to use the useState hook to determine when our sidebar should be open and when it shouldn’t be. We then create a function that allows us to toggle that state:

const [open, setOpen] = useState(false);

const toggleSidebar = () => setOpen(!open);

After this, we can now use the open state in our div styling to display/hide the sidebar as required:

<>
  <button className='border-none bg-none'>
    <FaBars />
  </button>
  <div className={
        open 
         ? 'absolute top-0 right-0 z-50 h-screen w-3/5 bg-white shadow-md' 
         : 'hidden'}>
    <div>
      {displayMenu()}
    </div>
  </div>
</>

Just to make sure we're on the same page, your Sidebar.js file should look like this so far:

//Sidebar.js

import styles from '@/styles/Home.module.css';
import { Inter } from '@next/font/google';
import Head from 'next/head';
import Image from 'next/image';
import Link from 'next/link';
import { useState } from 'react';
import { FaBars } from 'react-icons/fa';
import { useMedia } from 'react-use';

import Logo from '../images/loop.svg';

const inter = Inter({ subsets: ['latin'] });

// Menu
const menuItems = [
  {
    menu: 'About',
    link: '/about',
  },
  {
    menu: 'Product',
    link: '/product',
  },
  {
    menu: 'Services',
    link: '/services',
  },
  {
    menu: 'Contact Us',
    link: '/contact',
  },
];

export default function Home() {

const displayMenu = () => {
    return menuItems.map((item) => (
      <Link
        key={item.menu}
        href={item.link}
        className='pb-5 hover:text-yellow md:pb-0 md:pl-6'
      >
        {item.menu}
      </Link>
    ));
  };

    const isDesktop = useMedia('(min-width: 768px)', true);
  const [open, setOpen] = useState(false);

  const toggleSidebar = () => setOpen(!open);

  return (
    <>
      <Head>
        <title>Next & Tailwind Sidebar</title>
        <meta name='description' content='Generated by create next app' />
        <meta name='viewport' content='width=device-width, initial-scale=1' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <main>
        <div className='flex justify-between p-2 shadow-lg items-center'>
          {/* Logo */}
          <div>
            <Image src={Logo} alt='Logo' width={100} height={100} />
          </div>

          {/* Menu links */}
          <div className={inter.className}>
            <div className='text-red'>
              {isDesktop ? (
                displayMenu()
              ) : (
                <>
                  <button className='border-none bg-none'>
                    <FaBars />
                  </button>
                  <div
                    className={
                      open
                        ? 'absolute top-0 right-0 z-50 h-screen w-3/5 bg-white shadow-md'
                        : 'hidden'
                    }
                  >
                    <div>
                      {displayMenu()}
                    </div>
                  </div>
                </>
              )}
            </div>
          </div>
        </div>
      </main>
    </>
  );
}

You’ll notice that when you adjust your browser to a width lower than 768px, our beautiful burger menu icon is there to save the day. Now, for the final part of the project. We want the sidebar to show up when the burger menu is clicked on. We can achieve this with our toggleSidebar function we created to take care of our state change:

<button
  className='border-none bg-none'
  onClick={() => toggleSidebar()}
>
  <FaBars />
</button>

With this, our toggleSidebar function will be called when the burger menu is clicked on. However, you’ll notice that there’s no way to close the sidebar. To achieve that, we will include a close button depicted by an "X".

First, we’ll import an "X" symbol from react-icons, the one I’ve chosen for this project is GrClose which we can import like so:

import { GrClose } from 'react-icons/gr';

We can then use this import in our sidebar and also include some CSS to get everything looking good 😉

<>
  <button
  className='border-none bg-none'
  onClick={() => toggleSidebar()}
>
  <FaBars />
</button>
  <div className={
       open 
        ? 'absolute top-0 right-0 z-50 h-screen w-3/5 bg-white shadow-md' 
        : 'hidden'}>
    <div className='flex flex-col text-center'>
        <button
            className='mb-7 mt-7 mr-1 flex justify-end px-5 text-xl'
            onClick={() => toggleSidebar()}
        >
            <GrClose />
        </button>
      {displayMenu()}
    </div>
  </div>
</>

And that's all she wrote. Your Sidebar.js file should now look like this:

//Sidebar.js

import styles from '@/styles/Home.module.css';
import { Inter } from '@next/font/google';
import Head from 'next/head';
import Image from 'next/image';
import Link from 'next/link';
import { useState } from 'react';
import { FaBars } from 'react-icons/fa';
import { GrClose } from 'react-icons/gr';
import { useMedia } from 'react-use';

import Logo from '../images/loop.svg';

const inter = Inter({ subsets: ['latin'] });

// Menu
const menuItems = [
  {
    menu: 'About',
    link: '/about',
  },
  {
    menu: 'Product',
    link: '/product',
  },
  {
    menu: 'Services',
    link: '/services',
  },
  {
    menu: 'Contact Us',
    link: '/contact',
  },
];

export default function Home() {

  const displayMenu = () => {
    return menuItems.map((item) => (
      <Link
        key={item.menu}
        href={item.link}
        className='pb-5 hover:text-yellow md:pb-0 md:pl-6'
      >
        {item.menu}
      </Link>
    ));
  };

    const isDesktop = useMedia('(min-width: 768px)', true);
  const [open, setOpen] = useState(false);

  const toggleSidebar = () => setOpen(!open);

  return (
    <>
      <Head>
        <title>Next & Tailwind Sidebar</title>
        <meta name='description' content='Generated by create next app' />
        <meta name='viewport' content='width=device-width, initial-scale=1' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <main>
        <div className='flex justify-between p-2 shadow-lg items-center'>
          {/* Logo */}
          <div>
            <Image src={Logo} alt='Logo' width={100} height={100} />
          </div>

          {/* Menu links */}
          <div className={inter.className}>
            <div className='text-red'>
              {isDesktop ? (
                displayMenu()
              ) : (
                <>
                  <button
                    className='border-none bg-none'
                    onClick={() => toggleSidebar()}
                  >
                    <FaBars />
                  </button>
                  <div
                    className={
                      open
                        ? 'absolute top-0 right-0 z-50 h-screen w-3/5 bg-white shadow-md'
                        : 'hidden'
                    }
                  >
                    <div className='flex flex-col text-center'>
                      <button
                        className='mb-7 mt-7 mr-1 flex justify-end px-5 text-xl'
                        onClick={() => toggleSidebar()}
                      >
                        <GrClose />
                      </button>
                      {displayMenu()}
                    </div>
                  </div>
                </>
              )}
            </div>
          </div>
        </div>
      </main>
    </>
  );
}

Conclusion

At this juncture, it's safe to say that you're able to set up a new Next.js project, install Tailwind and begin your journey towards creating awesome navigation bars for your projects.

Thank you so much for following along. It's been a blast sharing this with you. I'd love your feedback as this is my first technical article 😁.

The project is live here and available on GitHub.

Until next time 👋🏾.