Blogs

Building a Dynamic Sidebar Component with React and Framer Motion

Sidebar Component

Demo

Sidebar Component Demo

Introduction

In modern web development, sidebars are essential for navigation and improving user experience. A well-designed sidebar can enhance your application's usability by providing a consistent and accessible navigation menu. In this guide, we'll build a dynamic sidebar component using React and Framer Motion. This component will include animations for opening and closing, making it both functional and visually appealing.

Prerequisites

Before we begin, make sure you have the following installed:

  • Node.js (v18.x or higher)
  • npm (v8.x or higher)
  • framer-motion

Setting Up the Project

Before we dive into building the sidebar, ensure you have a basic understanding of React and CSS. Familiarity with Framer Motion for animations will be helpful but not mandatory, as we will cover it step-by-step. You'll also need a React development environment set up with create-react-app or vite.

Creating the Sidebar Component

Now, let's install the necessary dependencies for our project:

bash

npm install framer-motion lucide-react

Creating the Sidebar Component

We'll start by creating a new file called Sidebar.tsx in our project directory. This file will contain the core logic for our sidebar component.

Importing Dependencies

tsx

import React, { useState, useMemo } from 'react'; import { motion } from 'framer-motion'; import { ChevronLeft, Home, LucideIcon, Package, Package2, Tag, User2, } from 'lucide-react'; import { cn } from '@/lib/utils';

The SidebarLink component renders individual links within the sidebar. Each link displays an icon and text.

tsx

type ILink = { title: string; href: string; icon: LucideIcon; }; const SidebarLink = React.memo(({ link }: { link: ILink }) => { const Icon = link.icon; return ( <li key={link.title} className="flex w-full cursor-pointer items-center gap-4 px-3.5 py-3 hover:bg-black hover:text-white" > <Icon className="h-6 w-6 flex-shrink-0" /> <motion.span className="text-sm transition-all">{link.title}</motion.span> </li> ); }); SidebarLink.displayName = 'SidebarLink';

Creating the Sidebar Component

Now, let's build the main Sidebar component, which manages the sidebar’s state and animations.

tsx

'use client'; import React, { useState, useMemo } from 'react'; import { motion } from 'framer-motion'; import { ChevronLeft, Home, LucideIcon, Package, Package2, Tag, User2, } from 'lucide-react'; import { cn } from '@/lib/utils'; type ILink = { title: string; href: string; icon: LucideIcon; }; const SidebarLink = React.memo(({ link }: { link: ILink }) => { const Icon = link.icon; return ( <li key={link.title} className="flex w-full cursor-pointer items-center gap-4 px-3.5 py-3 hover:bg-black hover:text-white" > <Icon className="h-6 w-6 flex-shrink-0" /> <motion.span className="text-sm transition-all">{link.title}</motion.span> </li> ); }); SidebarLink.displayName = 'SidebarLink'; export function SideBar() { const [isOpen, setOpen] = useState(false); const [isHovered, setHovered] = useState(false); const LINKS: ILink[] = useMemo( () => [ { title: 'Home', href: '#', icon: Home }, { title: 'Orders', href: '#', icon: Tag }, { title: 'Products', href: '#', icon: Package }, { title: 'Customers', href: '#', icon: User2 }, ], [] ); const handleMouseOver = () => { if (!isOpen) setHovered(true); }; const handleMouseLeave = () => { if (!isOpen) setHovered(false); }; const collapsedWidth = '3rem'; const expandedWidth = '16rem'; const width = isOpen ? expandedWidth : collapsedWidth; const shouldAnimateWidth = !isOpen || isHovered; const animateWidth = shouldAnimateWidth ? width : collapsedWidth; return ( <motion.aside className="relative z-50 hidden h-full md:block" animate={{ width: animateWidth }} transition={{ duration: isOpen ? 0 : 0.3, // No transition when opening, but smooth transition when closing type: isOpen ? 'just' : 'tween', // Tween for immediate, spring for smooth bounce: 0.3, damping: 10, stiffness: 50, }} initial={false} onMouseOver={handleMouseOver} onMouseLeave={handleMouseLeave} > <motion.div className={cn( 'h-full w-full overflow-hidden border-r bg-background shadow-lg', { 'absolute inset-0': isHovered, } )} animate={{ width: isOpen || isHovered ? expandedWidth : collapsedWidth }} initial={false} transition={{ duration: isHovered ? 0.2 : 0.3, type: 'tween', ease: isHovered ? 'easeInOut' : 'easeIn', }} > <div className="flex w-full items-center justify-between gap-4 px-3.5 py-5"> <Package2 className="h-6 w-6 flex-shrink-0" /> <button onClick={() => setOpen(!isOpen)} className={cn('rounded-full bg-black p-2 text-white', { 'pointer-events-none opacity-0': !isOpen && !isHovered, })} > <ChevronLeft className={cn('h-6 w-6 transition-transform', { 'rotate-180': isOpen, })} /> </button> </div> <nav className="w-full"> <ul className="flex w-full flex-col"> {LINKS.map(link => ( <SidebarLink key={link.title} link={link} /> ))} </ul> </nav> </motion.div> </motion.aside> ); }

Conclusion

In this guide, we built a fully animated sidebar component using React and Framer Motion. We covered the setup, component creation, and advanced animations, resulting in a visually appealing and functional sidebar. You can further customize the sidebar by adjusting animations, styles, or adding more features as needed.

Feel free to experiment with different Framer Motion configurations and styles to best fit your application’s design.