Dashboard
Welcome back! Here's what's happening with your projects today.
Total Users
2,543
+12.5%
Revenue
$45,231
+8.2%
Active Projects
12
+3
Completion Rate
94.2%
+2.1%
Recent Activity
Activity 1
2 hours ago
Activity 2
2 hours ago
Activity 3
2 hours ago
Activity 4
2 hours ago
Activity 5
2 hours ago
Quick Actions
Features
- ✅ Vertical layout - Fixed sidebar with scrollable content
- ✅ Grouped navigation - Organize items into categories
- ✅ Collapsible sidebar - YouTube-style collapse to icon-only view
- ✅ Active states - Highlight current page
- ✅ Badge support - Show counts or "New" labels
- ✅ Icons - Visual icons for each item
- ✅ Footer section - User profile and actions
- ✅ Tooltips - Show labels on hover when collapsed
- ✅ Mobile responsive - Slide-out drawer on mobile
- ✅ Smooth animations - Transitions and hover effects
- ✅ TypeScript support - Full type safety
Installation
Copy and paste the following code into your project.
import { LucideIcon, Menu, X, ChevronLeft } from "lucide-react";
import { useState } from "react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
interface NavItem {
label: string;
href: string;
icon?: LucideIcon;
badge?: string;
active?: boolean;
}
interface NavGroup {
title: string;
items: NavItem[];
}
interface FooterItem {
label: string;
sublabel?: string;
icon: LucideIcon;
href?: string;
onClick?: () => void;
}
interface SidebarNavigationProps {
logo?: React.ReactNode;
logoText?: string;
groups: NavGroup[];
footer?: React.ReactNode;
footerItems?: FooterItem[];
}
export function SidebarNavigation({
logo,
logoText = "Dashboard",
groups,
footer,
footerItems,
}: SidebarNavigationProps) {
const [mobileOpen, setMobileOpen] = useState(false);
const [collapsed, setCollapsed] = useState(false);
const SidebarContent = () => (
<>
<div
className={`flex items-center border-b border-zinc-800 p-6 ${
collapsed ? "justify-center" : "justify-between"
}`}
>
<div className="flex items-center gap-3">
{logo || (
<div className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-xl bg-blue-600 shadow-lg shadow-blue-600/30">
<span className="text-base font-bold text-white">
{logoText.charAt(0)}
</span>
</div>
)}
{!collapsed && (
<span className="text-xl font-bold text-white">{logoText}</span>
)}
</div>
{!collapsed && (
<button
onClick={() => setCollapsed(!collapsed)}
className="hidden rounded-lg p-2 text-zinc-500 transition-all hover:bg-zinc-800 hover:text-white lg:block"
aria-label="Collapse sidebar"
>
<ChevronLeft className="h-5 w-5" />
</button>
)}
</div>
<nav className="flex-1 space-y-6 overflow-y-auto p-4">
{groups.map((group, groupIndex) => (
<div key={groupIndex}>
{/* Group Title - Hide when collapsed */}
{!collapsed && (
<div className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-zinc-500">
{group.title}
</div>
)}
{/* Group Items */}
<div className="space-y-1">
{group.items.map((item, itemIndex) => {
const Icon = item.icon;
const linkContent = (
<a
href={item.href}
className={`group flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all ${
item.active
? "bg-blue-600 text-white shadow-lg shadow-blue-600/30"
: "text-zinc-400 hover:bg-zinc-800 hover:text-white"
} ${collapsed ? "justify-center" : ""}`}
>
{Icon && (
<Icon
className={`h-5 w-5 ${
item.active
? "text-white"
: "text-zinc-500 group-hover:text-zinc-400"
}`}
/>
)}
{!collapsed && (
<>
<span className="flex-1">{item.label}</span>
{item.badge && (
<span
className={`rounded-full px-2 py-0.5 text-xs font-semibold ${
item.active
? "bg-white/20 text-white"
: "bg-blue-500 text-white"
}`}
>
{item.badge}
</span>
)}
</>
)}
</a>
);
return collapsed ? (
<Tooltip key={itemIndex}>
<TooltipTrigger asChild>{linkContent}</TooltipTrigger>
<TooltipContent side="right">
<div className="flex items-center gap-2">
<span>{item.label}</span>
{item.badge && (
<span className="text-xs opacity-70">
{item.badge}
</span>
)}
</div>
</TooltipContent>
</Tooltip>
) : (
<div key={itemIndex}>{linkContent}</div>
);
})}
</div>
</div>
))}
</nav>
{footerItems ? (
<div className="border-t border-zinc-800 p-4">
<div className={collapsed ? "space-y-2" : "space-y-2"}>
{footerItems.map((item, index) => {
const Icon = item.icon;
const Element = item.href ? "a" : "button";
const content = (
<Element
key={index}
href={item.href}
onClick={item.onClick}
className={`flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-zinc-400 transition-all hover:bg-zinc-800 hover:text-white ${
collapsed ? "justify-center" : ""
}`}
>
<Icon className="h-5 w-5 flex-shrink-0 text-zinc-500" />
{!collapsed && (
<div className="flex-1 text-left">
<div className="text-sm font-medium text-white">
{item.label}
</div>
{item.sublabel && (
<div className="text-xs text-zinc-500">
{item.sublabel}
</div>
)}
</div>
)}
</Element>
);
return collapsed ? (
<Tooltip key={index}>
<TooltipTrigger asChild>{content}</TooltipTrigger>
<TooltipContent side="right">
<div>
<div className="font-medium">{item.label}</div>
{item.sublabel && (
<div className="text-xs opacity-70">
{item.sublabel}
</div>
)}
</div>
</TooltipContent>
</Tooltip>
) : (
content
);
})}
</div>
</div>
) : (
footer && (
<div
className={`border-t border-zinc-800 p-4 ${
collapsed ? "flex flex-col items-center gap-2" : ""
}`}
>
{footer}
</div>
)
)}
{collapsed && (
<div className="border-t border-zinc-800 p-4 flex justify-center">
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => setCollapsed(!collapsed)}
className="hidden rounded-lg p-2 text-zinc-500 transition-all hover:bg-zinc-800 hover:text-white lg:block"
aria-label="Expand sidebar"
>
<ChevronLeft className="h-5 w-5 rotate-180" />
</button>
</TooltipTrigger>
<TooltipContent side="right">
<p>Expand sidebar</p>
</TooltipContent>
</Tooltip>
</div>
)}
</>
);
return (
<TooltipProvider>
<button
onClick={() => setMobileOpen(!mobileOpen)}
className="fixed left-4 top-4 z-50 rounded-lg bg-zinc-900 p-2.5 text-zinc-300 shadow-lg transition-all hover:bg-zinc-800 hover:text-white lg:hidden"
aria-label="Toggle sidebar"
>
{mobileOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</button>
{mobileOpen && (
<div
className="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm lg:hidden"
onClick={() => setMobileOpen(false)}
/>
)}
<aside
className={`fixed left-0 top-0 z-40 flex h-screen flex-col border-r border-zinc-800 bg-zinc-950 shadow-2xl transition-all lg:translate-x-0 ${
mobileOpen ? "translate-x-0" : "-translate-x-full"
} ${collapsed ? "w-20" : "w-64"}`}
>
<SidebarContent />
</aside>
<div
className={`hidden flex-shrink-0 lg:block ${
collapsed ? "w-20" : "w-64"
}`}
/>
</TooltipProvider>
);
}Update the import paths to match your project setup.
Usage
Basic Usage
import SidebarNavigation from "@/components/ui/sidebar-navigation";
import { LayoutDashboard, Users, Settings } from "lucide-react";
export default function Page() {
return (
<div className="flex min-h-screen">
<SidebarNavigation
logoText="Dashboard"
groups={[
{
title: "Main",
items: [
{
label: "Dashboard",
href: "/dashboard",
icon: LayoutDashboard,
active: true,
},
{
label: "Users",
href: "/users",
icon: Users,
},
{
label: "Settings",
href: "/settings",
icon: Settings,
},
],
},
]}
/>
<main className="flex-1 p-8">
{/* Your content */}
</main>
</div>
);
}With Collapsible Groups
<SidebarNavigation
logoText="Admin"
groups={[
{
title: "Main",
items: [
{ label: "Dashboard", href: "/", icon: LayoutDashboard },
],
},
{
title: "Workspace",
collapsible: true, // Can be collapsed
items: [
{ label: "Projects", href: "/projects", icon: FolderOpen },
{ label: "Tasks", href: "/tasks", icon: CheckSquare },
],
},
]}
/>With Badges
<SidebarNavigation
groups={[
{
title: "Main",
items: [
{
label: "Inbox",
href: "/inbox",
icon: Inbox,
badge: "12", // Notification count
},
{
label: "Analytics",
href: "/analytics",
icon: BarChart3,
badge: "New", // Feature badge
},
],
},
]}
/>With Footer Items
import { User, LogOut } from "lucide-react";
<SidebarNavigation
logoText="Dashboard"
groups={[...]}
footerItems={[
{
label: "John Doe",
sublabel: "john@example.com",
icon: User,
href: "/profile",
},
{
label: "Sign Out",
icon: LogOut,
onClick: () => console.log("Sign out"),
},
]}
/>With Custom Footer (Legacy)
<SidebarNavigation
logoText="Dashboard"
groups={[...]}
footer={
<div className="space-y-2">
<a href="/profile" className="flex items-center gap-3 rounded-lg px-3 py-2.5">
<User className="h-5 w-5" />
<div>
<div className="text-sm font-medium">John Doe</div>
<div className="text-xs text-zinc-500">john@example.com</div>
</div>
</a>
<button className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5">
<LogOut className="h-5 w-5" />
<span>Sign Out</span>
</button>
</div>
}
/>Note: Use
footerItemsprop for proper collapse behavior. Thefooterprop doesn't adapt to collapsed state.
Props
SidebarNavigationProps
| Prop | Type | Default | Description |
|---|---|---|---|
logo | ReactNode | undefined | Custom logo component |
logoText | string | "Dashboard" | Logo text |
groups | NavGroup[] | Required | Navigation groups |
footer | ReactNode | undefined | Custom footer content (doesn't adapt to collapse) |
footerItems | FooterItem[] | undefined | Footer items (adapts to collapse state) |
NavGroup
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | Required | Group title |
items | NavItem[] | Required | Group items |
NavItem
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | Required | Item label |
href | string | Required | Item URL |
icon | LucideIcon | undefined | Item icon |
badge | string | undefined | Badge text |
active | boolean | false | Active state |
FooterItem
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | Required | Item label |
sublabel | string | undefined | Secondary text (e.g., email) |
icon | LucideIcon | Required | Item icon |
href | string | undefined | Link URL |
onClick | () => void | undefined | Click handler |
TypeScript Interface
interface SidebarNavigationProps {
logo?: React.ReactNode;
logoText?: string;
groups: NavGroup[];
footer?: React.ReactNode;
footerItems?: FooterItem[];
}
interface NavGroup {
title: string;
items: NavItem[];
}
interface NavItem {
label: string;
href: string;
icon?: LucideIcon;
badge?: string;
active?: boolean;
}
interface FooterItem {
label: string;
sublabel?: string;
icon: LucideIcon;
href?: string;
onClick?: () => void;
}Layout
The sidebar uses a fixed position layout:
- Sidebar Expanded: Fixed 256px width (w-64)
- Sidebar Collapsed: Fixed 80px width (w-20) - icon-only view
- Spacer: Hidden spacer for desktop layout
- Mobile: Slide-out drawer with overlay
- Content: Flex-1 for remaining space
Collapse Behavior
The sidebar features YouTube-style collapsible functionality:
- Collapse button: Located at top-right when expanded
- Expand button: Located at bottom when collapsed
- Icon-only view: Shows only icons with tooltips when collapsed
- Hidden elements: Group titles, labels, badges, and text are hidden when collapsed
- Footer adaptation: Footer items show only icons when collapsed (use
footerItemsprop) - Smooth transition: Width animates between expanded and collapsed states
Styling
- Solid colors - Clean zinc-950 background
- Active state - Blue-600 with shadow
- Icons - Zinc-500 with hover effects
- Badges - Blue solid or white/20 for active
- Animations - Smooth transitions
Responsive Behavior
- Desktop (lg+): Fixed sidebar always visible
- Mobile: Hidden by default, toggle button to open
- Overlay: Dark backdrop on mobile when open
Use Cases
Perfect for:
- Admin dashboards
- SaaS application panels
- Documentation sites
- Project management tools
- CRM systems
- Analytics platforms
- Content management systems
- Internal tools