DEV Community

Cover image for How to create an animation to open a menu with Tailwind in 5 steps
Maysa Salvalaio
Maysa Salvalaio

Posted on

How to create an animation to open a menu with Tailwind in 5 steps

Hey Devs! ๐ŸŒผ

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, to use it you just need to follow the instructions in the README file. Also, there is deploy 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!๐Ÿ˜„

1. Installing Tailwind in your project

So, first thing, you need to install Tailwind. I recommend you look at the documentation (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.

// 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;
Enter fullscreen mode Exit fullscreen mode

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.

// 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;
Enter fullscreen mode Exit fullscreen mode

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

// 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;
Enter fullscreen mode Exit fullscreen mode

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.

// 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: [],
}
Enter fullscreen mode Exit fullscreen mode

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.

// 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;
Enter fullscreen mode Exit fullscreen mode

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.โœจ

Latest comments (2)

Collapse
 
zhouyg profile image
zhou-yg

what different or any better with writing normal css animation ?

Collapse
 
maysab profile image
Maysa Salvalaio

Actually, it isn't any better, it all depends on what you prefer. I choose Tailwind because I was trying to learn and when I tried to do the animation I didn't find any content about it. If you want to use the normal CSS, it will work the same way, just with a style file extra where you have to "translate" the tailwind classes into conventional CSS.