Blox/
Vertical Navigation
A collapsible sidebar navigation with expandable sections, icons, badges, and user profile. Perfect for dashboards and admin panels.
CollapsibleExpandable SectionsBadge SupportUser Profile
Installation
Terminal
TSXReactTailwind
npx @uiblox/cli add vertical-navPreview
Brand
JD
Jane Doe
jane@example.com
Source Code
Copy this code into your project. Customize the nav items, colors, and styling as needed.
vertical-nav.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217"use client"; import { useState } from "react"; import Link from "next/link"; import { cn } from "@/lib/utils"; interface NavItem { label: string; href?: string; icon: React.ReactNode; badge?: string; children?: { label: string; href: string }[]; } const navItems: NavItem[] = [ { label: "Dashboard", href: "/dashboard", icon: <HomeIcon />, }, { label: "Projects", icon: <FolderIcon />, badge: "12", children: [ { label: "All Projects", href: "/projects" }, { label: "Active", href: "/projects/active" }, { label: "Archived", href: "/projects/archived" }, ], }, { label: "Team", icon: <UsersIcon />, children: [ { label: "Members", href: "/team/members" }, { label: "Invitations", href: "/team/invitations" }, ], }, { label: "Analytics", href: "/analytics", icon: <ChartIcon />, }, { label: "Settings", href: "/settings", icon: <SettingsIcon />, }, ]; export function VerticalNav() { const [expanded, setExpanded] = useState<string[]>(["Projects"]); const [collapsed, setCollapsed] = useState(false); const toggleSection = (label: string) => { setExpanded((prev) => prev.includes(label) ? prev.filter((l) => l !== label) : [...prev, label] ); }; return ( <aside className={cn( "flex flex-col h-screen bg-slate-900 text-white transition-all duration-300", collapsed ? "w-16" : "w-64" )} > {/* Logo */} <div className="flex items-center justify-between h-16 px-4 border-b border-slate-800"> {!collapsed && ( <span className="text-xl font-bold bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent"> Brand </span> )} <button onClick={() => setCollapsed(!collapsed)} className="p-2 rounded-lg hover:bg-slate-800 transition-colors" > <ChevronIcon className={cn("w-5 h-5 transition-transform", collapsed && "rotate-180")} /> </button> </div> {/* Navigation */} <nav className="flex-1 overflow-y-auto py-4 px-2 scrollbar-auto-hide"> {navItems.map((item) => ( <div key={item.label} className="mb-1"> {item.children ? ( <> <button onClick={() => toggleSection(item.label)} className={cn( "flex items-center w-full gap-3 px-3 py-2.5 rounded-lg text-slate-300 hover:bg-slate-800 hover:text-white transition-colors", expanded.includes(item.label) && "bg-slate-800/50" )} > <span className="flex-shrink-0">{item.icon}</span> {!collapsed && ( <> <span className="flex-1 text-left text-sm font-medium">{item.label}</span> {item.badge && ( <span className="px-2 py-0.5 text-xs bg-purple-500/20 text-purple-300 rounded-full"> {item.badge} </span> )} <ChevronDownIcon className={cn( "w-4 h-4 transition-transform", expanded.includes(item.label) && "rotate-180" )} /> </> )} </button> {!collapsed && expanded.includes(item.label) && ( <div className="ml-6 mt-1 space-y-1 border-l border-slate-700 pl-3"> {item.children.map((child) => ( <Link key={child.href} href={child.href} className="block px-3 py-2 text-sm text-slate-400 hover:text-white rounded-lg hover:bg-slate-800/50 transition-colors" > {child.label} </Link> ))} </div> )} </> ) : ( <Link href={item.href!} className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-300 hover:bg-slate-800 hover:text-white transition-colors" > <span className="flex-shrink-0">{item.icon}</span> {!collapsed && ( <span className="text-sm font-medium">{item.label}</span> )} </Link> )} </div> ))} </nav> {/* User Section */} <div className="border-t border-slate-800 p-4"> <div className={cn("flex items-center gap-3", collapsed && "justify-center")}> <div className="w-9 h-9 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-white font-semibold text-sm"> JD </div> {!collapsed && ( <div className="flex-1 min-w-0"> <p className="text-sm font-medium truncate">Jane Doe</p> <p className="text-xs text-slate-400 truncate">jane@example.com</p> </div> )} </div> </div> </aside> ); } // Icon components function HomeIcon() { return ( <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /> </svg> ); } function FolderIcon() { return ( <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" /> </svg> ); } function UsersIcon() { return ( <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /> </svg> ); } function ChartIcon() { return ( <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /> </svg> ); } function SettingsIcon() { return ( <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> </svg> ); } function ChevronIcon({ className }: { className?: string }) { return ( <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" /> </svg> ); } function ChevronDownIcon({ className }: { className?: string }) { return ( <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> </svg> ); }