INFINITYUI
HomeComponentsDocsTemplates
Star

Getting Started

  • Introduction
  • Installation
  • CLI
  • Audit

Navigation

  • Spotlight Navbar
  • Glass DockNEW
  • Animated Tab Bar
  • Circle Menu
  • Magnet Tabs
  • Animated Sidebar
  • Apple Spotlight
  • Page TOC RailNEW

Text

  • Flip Text
  • Glitch Text
  • Liquid Text
  • Flip Fade Text
  • Mask Cursor Effect

Cards

  • Glow Border Card
  • Testimonials Card
  • Interactive Book
  • Trading CardsNEW
  • Hover Image
  • Chain of ThoughtNEW
  • Masonry Grid
  • Image Pile
  • Staggered Grid

Inputs

  • AI InputNEW
  • OTP Input
  • Leave Rating

Buttons

  • Social Flip Button
  • Creepy Button

Loaders

  • Jelly Loader
  • Rolling Ball Scroll
  • Glowing Scroll

Backgrounds

  • Light Lines
  • Perspective Grid
  • Liquid Ocean
  • Eagle Vision
  • Flow Scroll
  • Horizontal Scroll

Overlays

  • PersonaNEW
  • Infinite Moving Cards
  • Masked Avatars
  • Stacked Logos
  • Icon Wheel
  • Pixelated CarouselNEW
  • Pixelated Image Trail
  • Flip Scroll
  • Interactive Folder
  • Animated Folder IconNEW
  • Stack Scroll
  • Rubik Cube
Navigation

Animated Sidebar

#sidebar#animated#docs
Dashboard
originalInfinity original

This component is authored directly in InfinityUI. No Obsidian counterpart was found during the audit.

Installation

Use the registry command to add the component source, and install any package dependencies if needed.

Infinity Registry

Install the component source into your project with the shadcn CLI.

npx shadcn@latest add https://infinityui-pearl.vercel.app/r/animated-sidebar
Package Dependencies

Install the npm packages used by this component source.

npm install @iconify/react clsx framer-motion tailwind-merge

Component Code

Copy and paste this code into your component file.

tsx
"use client";

import { useState } from "react";
import { motion } from "framer-motion";
import { Icon } from "@iconify/react";
import { cn } from "@/lib/utils";

interface SidebarItem {
    id: string;
    label: string;
    icon: string;
    badge?: number;
}

interface AnimatedSidebarProps {
    items: SidebarItem[];
    defaultOpen?: boolean;
}

export function AnimatedSidebar({ items, defaultOpen = true }: AnimatedSidebarProps) {
    const [open, setOpen] = useState(defaultOpen);
    const [activeId, setActiveId] = useState(items[0]?.id);

    return (
        <motion.aside
            animate={{ width: open ? 220 : 64 }}
            transition={{ type: "spring", stiffness: 400, damping: 35 }}
            className="relative flex flex-col h-full bg-zinc-900 border border-white/10 rounded-2xl overflow-hidden shadow-2xl"
        >
            {/* Toggle Button */}
            <div className="flex items-center justify-between px-4 h-14 border-b border-white/5 shrink-0">
                {open && (
                    <motion.span
                        initial={{ opacity: 0 }}
                        animate={{ opacity: 1 }}
                        exit={{ opacity: 0 }}
                        className="text-sm font-bold text-white tracking-tight"
                    >
                        Dashboard
                    </motion.span>
                )}
                <button
                    onClick={() => setOpen(!open)}
                    className={cn(
                        "w-8 h-8 rounded-lg flex items-center justify-center text-zinc-400 hover:text-white hover:bg-white/5 transition-colors",
                        !open && "mx-auto"
                    )}
                    aria-label={open ? "Collapse sidebar" : "Expand sidebar"}
                >
                    <motion.span animate={{ rotate: open ? 0 : 180 }}>
                        <Icon icon="solar:sidebar-minimalistic-linear" fontSize={18} />
                    </motion.span>
                </button>
            </div>

            {/* Nav Items */}
            <nav className="flex flex-col gap-1 p-2 flex-1">
                {items.map((item) => {
                    const isActive = activeId === item.id;
                    return (
                        <button
                            key={item.id}
                            onClick={() => setActiveId(item.id)}
                            className={cn(
                                "relative flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 w-full text-left group",
                                isActive
                                    ? "bg-indigo-500/20 text-indigo-300"
                                    : "text-zinc-500 hover:text-zinc-300 hover:bg-white/5"
                            )}
                        >
                            {isActive && (
                                <motion.span
                                    layoutId="sidebar-active"
                                    className="absolute inset-0 rounded-xl bg-indigo-500/15 border border-indigo-500/30"
                                    transition={{ type: "spring", stiffness: 500, damping: 40 }}
                                />
                            )}
                            <Icon icon={item.icon} fontSize={18} className="shrink-0 relative z-10" />
                            {open && (
                                <motion.span
                                    initial={{ opacity: 0 }}
                                    animate={{ opacity: 1 }}
                                    className="flex-1 truncate relative z-10"
                                >
                                    {item.label}
                                </motion.span>
                            )}
                            {open && item.badge && (
                                <span className="relative z-10 min-w-[20px] h-5 rounded-full bg-zinc-800 text-zinc-400 text-[10px] font-bold flex items-center justify-center px-1.5">
                                    {item.badge}
                                </span>
                            )}
                        </button>
                    );
                })}
            </nav>
        </motion.aside>
    );
}