Docs
Sidebar Navigation

Sidebar Navigation

A vertical sidebar navigation for dashboards, admin panels, and documentation sites. Features collapsible groups, badges, active states, and user profile footer.

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
                },
            ],
        },
    ]}
/>
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"),
        },
    ]}
/>
<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 footerItems prop for proper collapse behavior. The footer prop doesn't adapt to collapsed state.

Props

SidebarNavigationProps

PropTypeDefaultDescription
logoReactNodeundefinedCustom logo component
logoTextstring"Dashboard"Logo text
groupsNavGroup[]RequiredNavigation groups
footerReactNodeundefinedCustom footer content (doesn't adapt to collapse)
footerItemsFooterItem[]undefinedFooter items (adapts to collapse state)
PropTypeDefaultDescription
titlestringRequiredGroup title
itemsNavItem[]RequiredGroup items
PropTypeDefaultDescription
labelstringRequiredItem label
hrefstringRequiredItem URL
iconLucideIconundefinedItem icon
badgestringundefinedBadge text
activebooleanfalseActive state

FooterItem

PropTypeDefaultDescription
labelstringRequiredItem label
sublabelstringundefinedSecondary text (e.g., email)
iconLucideIconRequiredItem icon
hrefstringundefinedLink URL
onClick() => voidundefinedClick 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 footerItems prop)
  • 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