Create a Responsive Navbar using React and Tailwind

Overview

Even though we are at the end of 2021 and taking all that care to ensure that the app is responsive from mobile to desktop. The applications still have a web slant.

What I mean by this is that even knowing what types of components or what kind of behavior we should adopt to have a better adoption of our applications on the mobile platform, we continue to do the same.

Giving the example of a top navbar, as soon as we reach the resolution of the mobile we will have the famous hamburger menu, then in order to interact with each of the navigation elements we will have a dropdown or a full screen menu. When in fact in most cases just one tabbar was enough.

One of the sources I recommend reading is the Material Design documentation, in my opinion it's the best place to get knowledge through examples.

Today's example

The one I had for today's article is the creation of two navigation components, one of which will be a navigation bar at the top of the page as soon as we have resolutions higher than the tablet. But if the resolution is lower than Desktop we will have a Tabbar.

So that we have an idea of what I'm saying, at the end of this article I hope you get this final result:

As you may have noticed, in both components we ensure the navigation of the page where the user is, by adding a border to the element plus a very subtle gradient.

Let's code

The framework we are going to use today is Tailwind CSS and along with this framework we are going to use other tools such as classnames and react-icons.

npm install classnames react-icons

After that we will create a file with the name of the navigation elements that we are going to have.

// @src/data/navigation.js

export default ["Home", "Discover", "Store", "Inbox", "Profile"];

After that let's create our hook (just to abstract the logic from the selected navigation elements). Where the home page will be "Home" and then we will have a role responsible for changing the current route.

// @src/hooks/useNavigation.js
import { useState, useCallback } from "react";

const useNavigation = () => {
  const [route, setRoute] = useState("Home");

  const selectAction = useCallback(
    (option) => {
      if (route === option) return;
      setRoute(option);
    },
    [route]
  );

  return { currentRoute: route, setCurrentRoute: selectAction };
};

export default useNavigation;

Now we can start working on our components. Let's start by working on our navbar. These are the styles of our Navbar component:

/* @src/components/Navbar/Navbar.module.css */

.navbar {
  @apply hidden md:flex flex-row items-center justify-between px-8 h-18 rounded-b-3xl bg-white;
}

.logo {
  @apply text-5xl text-gray-800 -mb-1;
}

.navItems {
  @apply flex flex-row self-end h-12;
}

.navItem {
  @apply w-22 text-gray-400 hover:text-gray-700 cursor-pointer font-medium tracking-wide text-sm flex items-start justify-center;
}

.selectedNavItem {
  @apply text-gray-700 border-b-3 border-gray-700 bg-gradient-to-b from-white to-gray-100;
}

.actions {
  @apply bg-white hover:bg-gray-50 border-2 border-gray-900 text-sm text-gray-900 py-3 px-5 rounded-lg font-medium tracking-wide leading-none;
}

Our component will receive three props, the navigation elements, the current route and the function to define the current route. Then we will map the array elements to have each of the navigation elements present in our navbar as well as apply some conditional rendering using classNames so that we can join the classes.

// @src/components/Navbar/index.jsx
import React from "react";
import { CgMonday } from "react-icons/cg";
import classNames from "classnames";

import styles from "./Navbar.module.css";

const Navbar = ({ navigationData, currentRoute, setCurrentRoute }) => {
  return (
    <nav className={styles.navbar}>
      <span className={styles.logo}>
        <CgMonday />
      </span>
      <ul className={styles.navItems}>
        {navigationData.map((item, index) => (
          <li
            className={classNames([
              styles.navItem,
              currentRoute === item && styles.selectedNavItem,
            ])}
            key={index}
            onClick={() => setCurrentRoute(item)}
          >
            {item}
          </li>
        ))}
      </ul>
      <button className={styles.actions}>Logout</button>
    </nav>
  );
};

export default Navbar;

Now with the Navbar finished we can start working on our Tabbar. The styles are as follows:

/* @src/components/Tabbar/Tabbar.module.css */

.tabbar {
  @apply flex md:hidden flex-row items-center justify-around px-8 h-18 bg-white visible md:invisible fixed bottom-0 w-full rounded-t-3xl text-2xl;
}

.tabItem {
  @apply text-gray-400 hover:text-gray-700 cursor-pointer w-18 h-full flex items-center justify-center;
}

.tabItemActive {
  @apply bg-gradient-to-t from-white to-gray-100 border-t-3 border-gray-700 text-gray-700;
}

.icon {
  @apply -mb-1;
}

This component will receive exactly the same props as the Navbar but this time we have to make a pretty simple conditional rendering. In the mapping of array elements we have to render the icon indicated to the route so we will create a function with a switch that will be responsible for returning the icon according to the element.

// @src/components/Tabbar/index.jsx

import React, { useCallback } from "react";
import classNames from "classnames";
import { AiFillHome, AiFillCompass } from "react-icons/ai";
import { BsFillBagFill, BsFillPersonFill } from "react-icons/bs";
import { CgInbox } from "react-icons/cg";

import styles from "./Tabbar.module.css";

const Tabbar = ({ navigationData, currentRoute, setCurrentRoute }) => {
  const getTabIcon = useCallback((item) => {
    switch (item) {
      case "Home":
        return <AiFillHome />;
      case "Discover":
        return <AiFillCompass />;
      case "Store":
        return <BsFillBagFill />;
      case "Inbox":
        return <CgInbox />;
      case "Profile":
        return <BsFillPersonFill />;
    }
  }, []);

  return (
    <nav className={styles.tabbar}>
      {navigationData.map((item, index) => (
        <span
          key={index}
          className={classNames([
            styles.tabItem,
            currentRoute === item && styles.tabItemActive,
          ])}
          onClick={() => setCurrentRoute(item)}
        >
          <span className={styles.icon}>{getTabIcon(item)}</span>
        </span>
      ))}
    </nav>
  );
};

export default Tabbar;

Last but not least we have to go to our input file (which in this case is App.jsx) and we will have the following styles:

/* @src/App.module.css */

.container {
  @apply bg-gray-200 h-screen;
}

.devLogo {
  @apply flex items-center justify-center text-5xl text-gray-300 h-5/6;
}

Now in our App.jsx we will import our navigation data, our hook and each of the components that we create later, we will pass the indicated props to each one.

// @src/App.jsx
import React from "react";
import { FaDev } from "react-icons/fa";

import styles from "./App.module.css";
import useNavigation from "./hooks/useNavigation";
import navigationData from "./data/navigation";

import Navbar from "./components/Navbar";
import Tabbar from "./components/Tabbar";

const App = () => {
  const { currentRoute, setCurrentRoute } = useNavigation();

  return (
    <div className={styles.container}>
      <Navbar
        navigationData={navigationData}
        currentRoute={currentRoute}
        setCurrentRoute={setCurrentRoute}
      />
      <Tabbar
        navigationData={navigationData}
        currentRoute={currentRoute}
        setCurrentRoute={setCurrentRoute}
      />
      <div className={styles.devLogo}>
        <FaDev />
      </div>
    </div>
  );
};

export default App;

Conclusion

As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻‍💻

Hope you have a great day! 👋

18