How to create an animation to open a menu with Tailwind in 5 steps
Tags: react, tailwindcss, javascript, css
Published: 2022-08-02 23:46:28

**_Hey Devs!_** :blossom:

I'm a *dev student*, and I would like to start producing content that can help other people. Since I'm starting in the dev community, I can relate to many difficulties that starters have, and I know that some of them seem too simple and don't even have content explanations on the internet.

Today I will be helping you to create a **menu** just like the one in the cover gif. As a bônus, I also set the current page with a different color on the nav bar. In this article, I'll be using a **React** application, which means some of the processes may be different if you're using a different kind of framework (or none at all). To create the logic that changes the page style, I'll use **React Hooks**.

The project is available in this [repository](https://github.com/Maysa-B/timer-and-nav-tutorial), to use it you just need to follow the instructions in the README file. Also, there is [deploy](https://timer-and-nav-tutorial.vercel.app/) with the result if you want to see it.

I like to use this feature in my projects, however, I've never seen a tutorial explaining how to. So let's get started!:smile:

**1. Installing Tailwind in your project**

So, first thing, you need to install Tailwind. I recommend you look at the [documentation](https://tailwindcss.com/docs/installation) (aka your best friend when coding).

**2. Create the component**

Now, you have to create the **menu component**. When I use React, I prefer to make it apart and add it to the pages. Here is an example of a navigation menu that I produced in my timer's project.

```jsx
// component:  Nav
import  logo  from  '../assets/images/icon.png'
import  Button  from  '../components/Button';
import  LinkComponent  from  "./LinkComponent";

const  Nav  = () => {
	return (
		<nav>
			<Button
				func={() =>  setIsOpen(!isOpen)}
				txt={<img  alt="menu-burguer"  src={logo} />}
			/>
			<div>
				<LinkComponent  path={"/"} txt={"Home Page"} />
				<LinkComponent  path={"/countdown"} txt={"Countdown"} />
				<LinkComponent  path={"/timer"} txt={"Timer"} />
				<LinkComponent  path={"/settings"} txt={"Settings"} />
				<LinkComponent  path={"/about"} txt={"About"} />
			</div>
		</nav>
	);
}

export  default  Nav;

// component: Button
const  Button  = ({ func, txt, isDisabled, className }) => {
	return (
		<button
			className={className}
			disabled={isDisabled}
			type="button"
			onClick={  func  }
		>
			{  txt  }
		</button>
	);
}

Button.defaultProps  = {
	isDisabled:  false,
} 

export  default  Button;

// component: LinkComponent
import { Link } from  "react-router-dom"

const  LinkComponent  = ({ path, txt }) => {
	return (
		<Link
			to={path}
		>
			{txt}
		</Link>
	);
}

export  default  LinkComponent;
```

**3. Create the logic**
For this animation to work as we expect, it's necessary to have a code that allows a **change of the HTML classes**. That is important because you need a different animation when opening and closing de menu, besides, it's fundamental that the position set to the bar is the one where it's supposed to stay when the animation is over.

```jsx
// component:  Nav
import { useState } from  "react";
import  logo  from  '../assets/images/icon.png'
import  Button  from  '../components/Button';
import  LinkComponent  from  "./LinkComponent";

const  Nav  = () => {
// the state used to change the current situation (open or closed)
	const [isOpen, setIsOpen] =  useState(false);

	return (
		<nav>
			<Button
// when the "menu" button is clicked, it sets the state for the opposite boolean value
				func={() =>  setIsOpen(!isOpen)}
				txt={<img  alt="menu-burguer"  src={logo} />}
			/>
// this is the div where the menu is "hidden", so it's where the change of classes needs to happen
			<div
				className={isOpen  ? ('class for open menu') : ('class for closed menu')}
			>
				<LinkComponent  path={"/"} txt={"Home Page"} />
				<LinkComponent  path={"/countdown"} txt={"Countdown"} />
				<LinkComponent  path={"/timer"} txt={"Timer"} />
				<LinkComponent  path={"/settings"} txt={"Settings"} />
				<LinkComponent  path={"/about"} txt={"About"} />
			</div>
		</nav>
	);
}

export  default  Nav;
```

The bonus part, where the link to the current page is highlighted, it's a bit more complicated, since it needs more logic.

```jsx
// component: LinkComponent
import { useContext, useEffect, useState } from  "react";
import { Link, useRouteMatch } from  "react-router-dom"
import  TimeContext  from  "../context/TimeContext";

const  LinkComponent  = ({ path, txt }) => {
// first, we need to get the current pathname
	const  pathname  =  useRouteMatch();
	const [isCurrent, setIsCurent] =  useState(false);
	const [currentPath, setCurrentPath] =  useState('/');

// always when the pathname is changed the function occurs
	useEffect(() => {
		setCurrentPath(pathname.path)
	}, [pathname]);

// always when the pathname or the path (props) is changed the function occurs
	useEffect(() => {
		const  changeIsCurrent  = () => {
			if (currentPath  ===  path) {
				setIsCurent(true);
			} else {
				setIsCurent(false);
			}
		}
		changeIsCurrent();
	}, [currentPath, path]);  

	return (
// where happens the highlight depends if it's true (happen) or false (don't happen)
		<Link
			className={isCurrent  ? ('class when the page is the current'): ('class when the page is not the current')}
			to={path}
		>
			{txt}
		</Link>
	);
}

export  default  LinkComponent;
```

**4. Create the animation**

In the document tailwind.config.js, there's an object where you can add your custom settings. We're going to add our animation config, just like the example.

```js
// tailwind.config.js
/** @type  {import('tailwindcss').Config} */

module.exports  = {
	content: ["./src/**/*.{js,jsx,ts,tsx}"],
	theme: {
		extend: {
			animation: {
				openmenu:  'openmenu 1s ease-in',
				closemenu:  'closemenu 1s ease-in',
			},
			keyframes: {
				openmenu: {
				// initial position
					'0%': {left:  '-224px'},
				// final position
					'100%': {left:  '0px'}
				},
				closemenu: {
				// initial position
					'0%': {left:  '0px'},
				// final position
					'100%': {left:  '-224px'}
				},
			}
		},
	},
	plugins: [],
}
```

**5. Add the classes to the menu component**

Now that you have your animation, it's time to add it to the component. This one is the result of my timer's project, but you can style it the way you like.
Alert: Don't forget to set the **`final position`** from the animation as the **`default`** in your component. That is necessary because once the animation is over, it'll be redirected to the position set.

```jsx
// component:  Nav
import { useState } from  "react";
import  logo  from  '../assets/images/icon.png'
import  Button  from  '../components/Button';
import  LinkComponent  from  "./LinkComponent";

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

	return (
		<nav  className="font-epilogue">
			<Button
				className="w-20"
				func={() =>  setIsOpen(!isOpen)}
				txt={<img  alt="menu-burguer"  src={logo} />}
			/>
			<div  
				className={isOpen  ?  
				"left-[0px] font-epilogue flex top-[12vh] animate-openmenu w-56 absolute flex-col 	bg-light-ocean p-12 h-[88vh]" :  
				"animate-closemenu top-[12vh] left-[-224px] flex w-56 absolute flex-col bg-light-ocean p-12 h-[88vh]"}
			>
				<LinkComponent  path={"/"} txt={"Home Page"} />
				<LinkComponent  path={"/countdown"} txt={"Countdown"} />
				<LinkComponent  path={"/timer"} txt={"Timer"} />
				<LinkComponent  path={"/settings"} txt={"Settings"} />
				<LinkComponent  path={"/about"} txt={"About"} />
			</div>
		</nav>
	);
}

export  default  Nav;

// component: LinkComponent
import { useContext, useEffect, useState } from  "react";
import { Link, useRouteMatch } from  "react-router-dom"
import  TimeContext  from  "../context/TimeContext";

const  LinkComponent  = ({ path, txt }) => {
	const  pathname  =  useRouteMatch();
	const { currentPath } =  useContext(TimeContext);
	const [isCurrent, setIsCurent] =  useState(false);
	const [currentPath, setCurrentPath] =  useState('/');

	useEffect(() => {
		setCurrentPath(pathname.path)
	}, [pathname]);

	useEffect(() => {
		const  changeIsCurrent  = () => {
			if (currentPath  ===  path) {
				setIsCurent(true);
			} else {
				setIsCurent(false);
			}
		}
		changeIsCurrent();
	}, [currentPath, path]);  

	return (
		<Link
			className={isCurrent  ? (
			"mb-3 text-dark-purple font-bold bg-soft-purple p-2 text-center rounded-2xl"): (
			"mb-3 text-dark-purple hover:font-bold p-2 text-center")}
			to={path}
		>
			{txt}
		</Link>
	);
}

export  default  LinkComponent;
```

I hope I've enjoyed this tutorial. If you see something that can be improved, don't hesitate to get in touch with me! All feedback is very welcome.:sparkles: