DEV Community

Cover image for Create a Responsive Navbar using React and Tailwind
Francisco Mendes
Francisco Mendes

Posted on

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:

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


Enter fullscreen mode Exit fullscreen mode

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"];


Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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;
}


Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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;
}


Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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;
}


Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode




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! πŸ‘‹

Top comments (16)

Collapse
 
mapleleaf profile image
MapleLeaf

This is a good post, with a nice-looking end result. However, I'd like to strongly recommend against using @apply for all of the styling.

A nice part about tailwind is not having to come up with names for everything, along with the extra maintenance burden of additional stylesheets, imports, and so on. More room for accidental, possibly silent breakage.

Using the tailwind class names directly avoids this, and you don't really need the naming uniqueness from CSS modules either; that's just not a problem with tailwind.

tailwindcss.com/docs/extracting-co...

Collapse
 
franciscomendes10866 profile image
Francisco Mendes

The problem with having inline styles is the readability of your components, if you have hundreds of components in react with lots of inline styling it will be much harder to figure out what each css class does (unless they are small).

Exactly for this reason I like to use css classes, splitting jsx with css makes the approach much simpler and much more intuitive for anyone new to the project.

In the same way that CSS Modules help if you have some naming conventions in your team, such as the element that is at the top level of the component has the class "container" or "wrapper". As the CSS Modules assigns the unique name to the class, we end up having no friction with lots of classes with the similar name.

But in the midst of all this I think that what matters is the development experience and the adaptation time of a new programmer in the project or the possible maintenance in a space of 8 months (for example).

Thank you in advance for your comment and your feedback! πŸ₯³

Collapse
 
reikrom profile image
Rei Krom

Using tailwind in css modules seems like added complexity with little benefit.

Readability is lost due to everything being inline.
Utility classes are lost and converted into simple css which results in the same bundle size.

I'm really trying to like tailwind but the more i use it the more flaws it seems to have.

Thread Thread
 
franciscomendes10866 profile image
Francisco Mendes

Actually CSS Modules don't add any complexities. This is like everything, many like to use CSS frameworks, others prefer CSS-in-JS, others prefer libraries like Chakra UI.

I think this comes from the preferences of each one, but for example Tailwind pleases me because I can use it for web and mobile (there are dependencies on the community to use with React Native), the website load is fast because it's CSS and because the bundle build size gets smaller.

And with classNames the join of classes becomes simple even using conditional rendering.

Thread Thread
 
reikrom profile image
Rei Krom

I mean using tailwindcss inside modules adds complecity not using modules itself. Each new className gets it's own css from tailwind @apply. Which would mean the more classNames you've used with @apply the bigger your bundle size will get as you are not using utility classes anymore, but just compiling tailwind into normal css.

[number of classNames] * [number of @apply] will resuly in that much more css being generated as opposed to having tailwind as a classname where it reuses the same utility in many different places.

Image description

Thread Thread
 
mapleleaf profile image
MapleLeaf

One thing I like to do is separate aesthetic readability from how easy the code is to understand.

Aesthetic readability is how visually pleasing the code looks. How "clean" and "neat" it is. This is something that tools like Prettier help with, and something that Tailwind definitely is not good at πŸ˜…

However, tailwind excels at understandability. If you ask the question "what styles does this element have", that question can't be answered any more immediately than with inline styling: you have your answer right on the element you're looking at! Other solutions require you to go to another part of the file at best, an entirely separate file at worst. Lots of context switching, lots of tedium.

The aesthetic hurdle is still too big for some, and I understand that. But this understanding of "readability" hopefully helps to see where Tailwind lovers are coming from 😊

Thread Thread
 
reikrom profile image
Rei Krom

I agree that once I got over the initial learning curve of tailwind. It does look better not having property, value pairs in the css.
Having said that i can't quite recommend using tailwindcss just for the aestetics as it fails in reducing bundle size. It has issues with linting.

Tailwindcss is soo close to being amazing, tho sadly not yet for me.
Devs should definitely not have free access to give arbitrary values to margins paddings and Font sizes, and the way tailwind let's you easily build your own design system is really nice, but the implementation is not quite there yet.

If sass/scss allowed $variables / % placeholders to contain key value pairs or whole blocks of styles. Then tailwindcss would be in trouble.

Thread Thread
 
mapleleaf profile image
MapleLeaf

it fails in reducing bundle size.

If we compare using tailwind vs. regular CSS, regular CSS would end up having the larger bundle size, right? With tailwind, you're reusing the same class name for any bit of styling. With regular CSS, you make a new class name which could share some styling with other existing class names. If you mean the size of the HTML, the repetition is very kind to gzipping and other compression algos.

It has issues with linting.

What issues? The VSCode TailwindCSS extension, for example, warns if you try to apply multiple classes with conflicting values. Also gives you nice autocomplete.

Devs should definitely not have free access to give arbitrary values to margins paddings and Font sizes

Hopefully I understand you correctly. Are you saying that tailwind does give you arbitrary values? Because half the point of tailwind is to not. The range of values is large, for sure, but it's still a much more limited subset of values than in any traditional CSS codebase. See here under "Enforced Consistency". The post is a good read overall.

Tailwind JIT does add an arbitrary values feature, e.g. p-[13px], but it's considered a last resort. Sometimes you do need some random one-off value. The style attribute was always used for that, too.

If sass/scss allowed $variables / % placeholders to contain key value pairs or whole blocks of styles. Then tailwindcss would be in trouble.

I don't see the connection here, or why this would put tailwind "in trouble". Regardless of what features come to Sass, it would still be based around writing arbitrary styles in separate files; utility CSS users want to avoid that.

Thread Thread
 
reikrom profile image
Rei Krom

If we compare using tailwind vs. regular CSS, regular CSS would end up having the larger bundle size, right? With tailwind, you're reusing the same class name for any bit of styling. With regular CSS, you make a new class name which could share some styling with other existing class names. If you mean the size of the HTML, the repetition is very kind to gzipping and other compression algos.

If you're using tailwindCss with @apply no, it is compiled to regular css, and end result is literally the same as just writing css. look at previous comments in the thread for the picture output.
Using tailwind in html jsx or tsx, it horribly reduces readability and in the docs tells you if you don't like it use apply to solve the problem it creates.

What issues? The VSCode TailwindCSS extension, for example, warns if you try to apply multiple classes with conflicting values. Also gives you nice autocomplete.

I'm mainly talking about using tailwind inside css files.
You need disable standard vsCode linting and replace it with another extension to fix it.

Hopefully I understand you correctly. Are you saying that tailwind does give you arbitrary values? Because half the point of tailwind is to not. The range of values is large, for sure, but it's still a much more limited subset of values than in any traditional CSS codebase. See here under "Enforced Consistency". The post is a good read overall.

My point was in favour for tailwind as it makes it easier to use set values provided by default by tailwind or if you got someone knowledgable that has added consistent styles to the tailwind config.

Tailwind JIT does add an arbitrary values feature, e.g. p-[13px], but it's considered a last resort. Sometimes you do need some random one-off value. The style attribute was always used for that, too.

That is a nice feature, but it at the moment fails if you need to use % instead.

I don't see the connection here, or why this would put tailwind "in trouble". Regardless of what features come to Sass, it would still be based around writing arbitrary styles in separate files; utility CSS users want to avoid that.

Assuming you use tailwind only in css to improve html readability (seperation of concerns and all that) the only benefit at the moment tailwind provides is shortening key value css pairs to single variables inside @apply.
If scss supported something similar, tailwind would be in trouble as tailwind would lose any benefit it can provide inside css.

Thread Thread
 
mapleleaf profile image
MapleLeaf

Ah, so those points were in reference to using tailwind in css with @apply. My first comment was actually recommending not to do that for various reasons πŸ˜‚

I agree that using Tailwind in CSS files with @apply is not much better than using regular CSS; if you're doing it that way, you may as well not use Tailwind.

Using tailwind in html jsx or tsx, it horribly reduces readability

I feel like people are better off getting used to the reduced aesthetic cleanliness, there are mountains of benefits when one does. I've read so many testimonials of "at first it looked horrifying, but after using it, I love it!", including from myself 😊

and in the docs tells you if you don't like it use apply to solve the problem it creates.

Assuming you mean this section of the docs, it says to use this feature to reduce repetition, not to make your HTML cleaner. Visual HTML cleanliness is a complete non-goal with tailwind. That's just something people co-opted when they saw @apply as a "solution" to Tailwind looking weird and unusual.

This part of the docs specifically says not to rely on class names to extract components.

If scss supported something similar, tailwind would be in trouble as tailwind would lose any benefit it can provide inside css.

To be fair, SCSS already has features like parametric mixins and functions that let you mimic what @apply can do, so Tailwind is already "in trouble" in that sense. But generally? Nah. Even then, I personally haven't had a use for Sass in ages, even before I started using Tailwind.

 
franciscomendes10866 profile image
Francisco Mendes • Edited

Now I understand what you wanted to say (sorry for being slow), it is without a doubt one of the negative aspects. In that respect I completely agree with you.

Collapse
 
lofibytes profile image
Jillian S. Estrella (she/her)

+1

Collapse
 
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

Visually, this looks great! Just a word of advice, you'll want to use anchors for the tabs rather than just list items. You can nest an a inside each list item and style it accordingly. This allows for keyboard navigation, as well as use of attributes like aria-current="page" to designate the active page. It also means you no longer need an onClick handler and can just use an href. (Note that most client-side routing libraries will also expose a custom Link component, so you could use one of those instead.)

Collapse
 
franciscomendes10866 profile image
Francisco Mendes

Yes I would do just that, but unfortunately I didn't have the react-router installed in my project. πŸ€“ But anyway thanks for your comment, it's informative and complements the article! 😊

Collapse
 
apexiodacoder profile image
ApexioDaCoder

Could you please add a link to a GitHub repository with the code? Thanks.

Collapse
 
rami_ois_2f7d7548c883fe72 profile image
Rami ois • Edited

Can you explain with the same layout but using CSS, HTML and JavaScript ?..