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
ReactTailwind
TSX
npx @uiblox/cli add vertical-nav

Preview

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
"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> ); }