Creating an animated hamburger menu in NextJS & Tailwind CSS

Jacob Hocker
4 min readApr 26, 2023

There are plenty of NPM packages out there that one can use to implement a hamburger menu in their responsive application. These packages however do lack customization ability that I prefer so feel free to following along the simple process of making a hamburger menu in NextJS and Tailwind CSS!

First thing is first to make sure the menu has the desired effect we will need to use state to manage whether our menu is open or closed. We do this by importing state from React and setting our state variable and setter function. We default it to false so when a user refreshes the page the menu or nav will be closed.

import React, { useState } from 'react';

export default const Nav = () => {
const [isOpen, setIsOpen] = useState(false);

After state is completed we will need to create a function that will handle our click.

import React, { useState } from 'react';

export default const Nav = () => {
const [isOpen, setIsOpen] = useState(false);

//Handles the opening and closing of our nav
const handleClick = () => {
setIsOpen(!isOpen);
};

The reason why we have setIsOpen(!isOpen) is so that every time this function is called that it gives us the inverse of the state boolean. So one click sets isOpen(true) and two clicks sets isOpen(false) .

Now we are ready to handle the JSX portion of this. We will create our menu using the button element. Keep in mind all the colors, sizes, animations, and time durations are just what I use. Everything can be customized I encourage you to play around with it and have fun!


return(

<button>
<span></span>
<span></span>
<span></span>
</button>

)

This are the elements we are going to use the button, as well as, the three <span> tags which will remain empty to make up our three lines in our hamburger. So lets call our handleClick function and put some CSS in the button element.

return(

<button onClick={handleClick}
className="flex flex-col justify-center items-center">
<span></span>
<span></span>
<span></span>
</button>

)

So we have called our handleClick so now every time the button is clicked the state will change to the inverse boolean value.

(Note: I create my applications with a mobile first responsive design. In the CSS styling there would be something along the lines of lg:hidden so that the menu will be hidden after a certain breakpoint.)

Now it’s time to style the <span> elements.

return(

<button onClick={handleClick}
className="flex flex-col justify-center items-center">
<span className={`bg-steel-500 block transition-all duration-300 ease-out
h-0.5 w-6 rounded-sm`} >
</span>
<span className={`bg-steel-500 block transition-all duration-300 ease-out
h-0.5 w-6 rounded-sm my-0.5`} >
</span>
<span className={`bg-steel-500 block transition-all duration-300 ease-out
h-0.5 w-6 rounded-sm`} >
</span>

</button>

)

(Note: The class names are going to be decently long for the <span> elements so I have broke them into multiple lines. However if you choose they can all be on one line or broken into multiple whatever is more readable to you)

So now that we have our styles set up for the <span> elements we should be able to visibly see them now on our page. However if you click your button you will see that nothing happens visually even though the state is being changed. To show that the state is being changed you can console.log(isOpen) and you will see it switching between true or false on every click.

Now we can add the ternary expressions to update the visuals based on whether isOpen is returning true or false!

return(

<button onClick={handleClick}
className="flex flex-col justify-center items-center">
<span className={`bg-steel-500 block transition-all duration-300 ease-out
h-0.5 w-6 rounded-sm ${isOpen ?
'rotate-45 translate-y-1' : '-translate-y-0.5'
}`} >
</span>
<span className={`bg-steel-500 block transition-all duration-300 ease-out
h-0.5 w-6 rounded-sm my-0.5 ${isOpen ?
'opacity-0' : 'opacity-100'
}} >
</span>
<span className={`bg-steel-500 block transition-all duration-300 ease-out
h-0.5 w-6 rounded-sm ${isOpen ?
'-rotate-45 -translate-y-1' : 'translate-y-0.5'
}} >
</span>

</button>

)

And there you have it! You just made an animated a hamburger menu button for a nav or whatever menu system you would like. Here is a look at what your full code should look like!

import React, { useState } from 'react';

export default const Nav = () => {
const [isOpen, setIsOpen] = useState(false);


const handleClick = () => {
setIsOpen(!isOpen);
};

return(

<button onClick={handleClick}
className="flex flex-col justify-center items-center">
<span className={`bg-steel-500 block transition-all duration-300 ease-out
h-0.5 w-6 rounded-sm ${isOpen ?
'rotate-45 translate-y-1' : '-translate-y-0.5'
}`} >
</span>
<span className={`bg-steel-500 block transition-all duration-300 ease-out
h-0.5 w-6 rounded-sm my-0.5 ${isOpen ?
'opacity-0' : 'opacity-100'
}} >
</span>
<span className={`bg-steel-500 block transition-all duration-300 ease-out
h-0.5 w-6 rounded-sm ${isOpen ?
'-rotate-45 -translate-y-1' : 'translate-y-0.5'
}} >
</span>

</button>

)
};

I hope this helped! Happy coding!

--

--

Jacob Hocker

Full Stack Software Engineer | UX/UI Designer | Technical Support Specialist