Open In App

How to Create Multi Level Sidebar Animation in React ?

Last Updated : 09 Jan, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In this article, we will see how we can animate the Multi-level sidebar in ReactJS to enhance the overall appearance of the Sidebar component in the application. This attracts the user towards the interface more and also increases the look and feel of the application.

We will discuss the following two approaches to implement Multi Level Sidebar Animation in React.

Steps to Create the React Application:

Step 1: Create a React application using the following command.

npx create-react-app foldername

Step 2: Once done, change your directory to the newly created application using the following command.

cd foldername

Step 3: Install the required packages by executing the below command in the terminal.

npm install framer-motion react-icons

Project Structure:

The updated dependencies in package.json for a frontend will look like this:

"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4",
"framer-motion": "^10.17.6",
"react-icons": "^4.12.0"
}

Approach 1: Using framer-motion

In this approach, we are using the Framer Motion animation where we have specified the state management to control the toggling of the sidebar, also we are performing the UI animation on the sidebar contents.

Example 1: Below is the implementation of above discussed approach.

Javascript




//App.js
import React, { useState } from "react";
import { motion } from "framer-motion";
import "./App.css";
 
const dataSide = [
    {
        id: 1,
        title: "Home",
        subItems: [],
    },
    {
        id: 2,
        title: "Topics",
        subItems: [
            { id: 21, title: "React" },
            { id: 22, title: "JavaScript" },
            { id: 23, title: "CSS" },
        ],
    },
    {
        id: 3,
        title: "About",
        subItems: [
            { id: 31, title: "GeeksforGeeks" },
            { id: 32, title: "Team" },
        ],
    },
    {
        id: 4,
        title: "More",
        subItems: [
            { id: 41, title: "Animations" },
            { id: 42, title: "Components" },
        ],
    },
];
 
const App = () => {
    const [item_Select, set_Item_Select] = useState(null);
    const [sideBar_Open, set_SideBar_Open] = useState(false);
    const dataClickFn = (i) => {
        set_Item_Select(i.id === item_Select?.id ? null : i);
    };
    const sideBarFn = () => {
        set_SideBar_Open(!sideBar_Open);
    };
    return (
        <div className={`app ${sideBar_Open ? "sidebar-open" : ""}`}>
            <div className="sidebar">
                {sideBar_Open && (
                    <>
                        <h1 className="logo">GeeksforGeeks</h1>
                        <motion.div
                            className="approach-title"
                            animate={{ opacity: 1, y: 0 }}
                            initial={{ opacity: 0, y: -20 }}
                        >
                            <h3>Approach 1: Using Framer Motion</h3>
                        </motion.div>
                        {dataSide.map((item) => (
                            <motion.div
                                key={item.id}
                                className={`sidebar-item ${
                                    item_Select?.id === item.id ? "active" : ""
                                }`}
                                onClick={() => dataClickFn(item)}
                                whileHover={{ scale: 1.1 }}
                            >
                                {item.title}
                                {item.subItems.length > 0 && (
                                    <motion.div
                                        initial={false}
                                        animate={{
                                            height:
                                                item_Select?.id === item.id
                                                    ? "auto"
                                                    : 0,
                                        }}
                                        className="sub-items"
                                    >
                                        {item.subItems.map((subItem) => (
                                            <motion.div
                                                key={subItem.id}
                                                className="sub-item"
                                            >
                                                {subItem.title}
                                            </motion.div>
                                        ))}
                                    </motion.div>
                                )}
                            </motion.div>
                        ))}
                    </>
                )}
                <motion.button
                    className="toggle-button"
                    whileHover={{ scale: 1.1 }}
                    onClick={sideBarFn}
                >
                    {sideBar_Open ? "Close Sidebar" : "Open Sidebar"}
                </motion.button>
            </div>
            <div className="content">
                <motion.h1
                    className="content-title"
                    animate={{ opacity: 1, y: 0 }}
                    initial={{ opacity: 0, y: -20 }}
                >
                    Welcome to GeeksforGeeks
                </motion.h1>
                <motion.h3
                    className="content-subtitle"
                    animate={{ opacity: 1, y: 0 }}
                    initial={{ opacity: 0, y: -20 }}
                >
                    Explore our amazing content
                </motion.h3>
            </div>
        </div>
    );
};
export default App;


CSS




body {
  margin: 0;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  display: flex;
  height: 100vh;
  overflow: hidden;
  background-color: #f0f0f0;
}
.app {
  display: flex;
  flex: 1;
}
.sidebar {
  width: 60px;
  background-color: #333;
  color: white;
  padding: 20px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.logo {
  color: #4caf50;
  font-size: 24px;
  margin-bottom: 20px;
  transition: font-size 0.5s ease;
}
.approach-title {
  margin-bottom: 20px;
}
.sidebar-item {
  cursor: pointer;
  margin-bottom: 10px;
  transition: background-color 0.3s ease;
}
.sidebar-item.active {
  background-color: #555;
}
.sub-items {
  overflow: hidden;
}
.sub-item {
  padding-left: 20px;
  margin-top: 5px;
  cursor: pointer;
}
.toggle-button {
  cursor: pointer;
  background-color: #4caf50;
  color: white;
  border: none;
  padding: 10px;
  margin-top: 20px;
  border-radius: 5px;
  outline: none;
  transition: margin-top 0.5s ease;
}
.sidebar-open .sidebar {
  width: 300px;
}
.sidebar-open .logo {
  font-size: 18px;
}
.sidebar-open .toggle-button {
  margin-top: 30px;
}
.sidebar-open .sub-item {
  padding-left: 20px;
}
.sidebar:not(.sidebar-open) {
  overflow: hidden;
  width: 400px;
  transition: width 0.5s ease;
}
.sidebar:not(.sidebar-open) .toggle-button {
  margin-top: 0;
}
.sidebar:not(.sidebar-open) .sub-item {
  padding-left: 10;
}
.content {
  flex: 1;
  padding: 20px;
  background-color: #fff;
  overflow-y: auto;
}
.content-title {
  color: #333;
  font-size: 32px;
  margin-bottom: 10px;
}
.content-subtitle {
  color: #555;
  font-size: 18px;
  margin-bottom: 20px;
}


Step to run the application: Open the terminal and type the following command.

npm start

Output: Open the browser and our project is shown in the URL http://localhost:3000/

Output1

Approach 2: Using CSS Only

In this approach, we are animating the multilevel sidebar by using CSS styling properties and keyframes. We are not using any other animation package like Approach 1. We have created all the animations by using CSS properties only.

Example 1: Below is the implementation of above discussed approach.

Javascript




// App.js
import React, { useState } from "react";
import { FaHome, FaBook, FaLaptop, FaBookOpen, FaTools } from "react-icons/fa";
import { BsFillCaretDownFill } from "react-icons/bs";
import "./App.css";
const SidebarItem = ({ item, onClick }) => {
    const [isOpen, setIsOpen] = useState(false);
    const itemClickFn = () => {
        setIsOpen(!isOpen);
        if (onClick) {
            onClick(item);
        }
    };
    const getIcon = (label) => {
        const iconMap = {
            Home: <FaHome />,
            Topics: <FaBook />,
            Languages: <FaLaptop />,
            About: <FaBookOpen />,
            Frameworks: <FaTools />,
        };
        return iconMap[label] || <BsFillCaretDownFill />;
    };
    return (
        <div
            className={`sidebar-item ${isOpen ? "open" : ""}`}
            onClick={itemClickFn}
        >
            <span className="icon">{getIcon(item.label)}</span>
            <span>{item.label}</span>
            {isOpen && item.children && (
                <div className="sub-menu">
                    {item.children.map((child, index) => (
                        <SidebarItem key={index} item={child} />
                    ))}
                </div>
            )}
        </div>
    );
};
const App = () => {
    const data = [
        { label: "Home" },
        {
            label: "Topics",
            children: [
                { label: "React" },
                { label: "JavaScript" },
                { label: "CSS" },
            ],
        },
        {
            label: "Languages",
            children: [
                {
                    label: "Front-end",
                    children: [
                        { label: "HTML" },
                        { label: "CSS" },
                        { label: "JavaScript" },
                    ],
                },
                {
                    label: "Back-end",
                    children: [
                        { label: "Node.js" },
                        { label: "Python" },
                        { label: "Ruby" },
                    ],
                },
            ],
        },
        { label: "About" },
        {
            label: "Frameworks",
            children: [
                { label: "React" },
                { label: "Angular" },
                { label: "Vue.js" },
            ],
        },
    ];
    return (
        <div className="app">
            <div className="sidebar">
                {data.map((item, index) => (
                    <SidebarItem key={index} item={item} />
                ))}
            </div>
            <div className="content">
                <h1>GeeksforGeeks</h1>
                <h3>Approach 2: Using CSS Only</h3>
            </div>
        </div>
    );
};
export default App;


CSS




.app {
    display: flex;
    height: 100vh;
    overflow: hidden;
}
 
.sidebar {
    width: 200px;
    background: linear-gradient(to right, #3f3f3f, #1a1a1a);
    color: white;
    padding: 20px;
    transition: width 0.3s ease, box-shadow 0.3s ease;
    box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
}
 
.sidebar-item {
    cursor: pointer;
    margin-bottom: 10px;
    transition: all 0.3s ease;
    position: relative;
    animation: bounce 0.5s ease;
}
 
@keyframes bounce {
 
    0%,
    20%,
    50%,
    80%,
    100% {
        transform: translateY(0);
    }
 
    40% {
        transform: translateY(-10px);
    }
 
    60% {
        transform: translateY(-5px);
    }
}
 
.sidebar-item span {
    display: inline-block;
    transition: transform 0.3s ease, text-shadow 0.3s ease;
}
 
.sidebar-item:hover {
    margin-left: 10px;
    box-shadow: 5px 0px 15px rgba(0, 0, 0, 0.3);
    background: linear-gradient(to right, #555, #444);
    border-radius: 10px;
    color: #ffeb3b;
}
 
.sidebar-item:hover span {
    transform: translateX(5px);
    text-shadow: 2px 2px 5px rgba(255, 255, 255, 0.5);
}
 
.sidebar-item:after {
    content: '';
    display: block;
    height: 2px;
    width: 0;
    background: rgb(224, 0, 0);
    transition: width 0.3s ease;
}
 
.sidebar-item:hover:after {
    width: 100%;
}
 
.open {
    font-weight: bold;
}
 
.sub-menu {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease, opacity 0.3s ease;
    animation: fadeIn 0.5s ease;
}
 
.sidebar-item.open .sub-menu {
    max-height: 500px;
    opacity: 1;
}
 
.sub-menu-item {
    opacity: 0;
    transform: translateY(-10px);
    transition: opacity 0.5s ease, transform 0.5s ease;
    color: #4caf50;
}
 
.sub-menu-item.show {
    opacity: 1;
    transform: translateY(0);
}
 
@media screen and (max-width: 768px) {
    .app {
        flex-direction: column;
    }
 
    .sidebar {
        width: 100%;
        position: absolute;
        left: -100%;
        transition: left 0.3s ease;
    }
 
    .sidebar.open {
        left: 0;
    }
 
    .sidebar-item {
        margin-left: 0;
    }
 
    .content {
        opacity: 0;
        transition: opacity 0.3s ease;
    }
 
    .content.open {
        opacity: 1;
    }
 
    .hamburger {
        display: block;
        cursor: pointer;
        order: -1;
    }
}
 
.sidebar.open {
    width: 250px;
}
 
.content {
    color: green;
    flex: 1;
    padding: 20px;
    background-color: #f4f4f4;
}


Step to run the application: Open the terminal and type the following command.

npm start

Output: Open the browser and our project is shown in the URL http://localhost:3000/

Output2



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads