Navigation
Animated Sidebar
#sidebar#animated#docs
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-sidebarPackage Dependencies
Install the npm packages used by this component source.
npm install @iconify/react clsx framer-motion tailwind-mergeComponent 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>
);
}