Sidebar
A composable, themeable and customizable sidebar component.
Demo
Dependencies
lucide-reactclsxtailwind-merge
Props
This is a base primitive. It accepts standard HTML attributes plus className for styling via cn().
Source
"use client";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { ChevronLeft, Menu } from "lucide-react";
export interface SidebarItem {
label: string;
href?: string;
icon?: React.ReactNode;
active?: boolean;
items?: SidebarItem[];
}
export interface SidebarProps {
items: SidebarItem[];
className?: string;
defaultCollapsed?: boolean;
}
export function Sidebar({
items,
className,
defaultCollapsed = false,
}: SidebarProps) {
const [collapsed, setCollapsed] = useState(defaultCollapsed);
const [expandedSections, setExpandedSections] = useState<string[]>([]);
const toggleSection = (label: string) => {
setExpandedSections((prev) =>
prev.includes(label) ? prev.filter((s) => s !== label) : [...prev, label],
);
};
const renderItems = (sidebarItems: SidebarItem[], depth = 0) => {
return sidebarItems.map((item) => (
<div key={item.label}>
{item.items ? (
<div>
<button
onClick={() => toggleSection(item.label)}
className={cn(
"flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-gray-100",
item.active ? "bg-gray-100 text-gray-900" : "text-gray-600",
collapsed && "justify-center",
)}
title={collapsed ? item.label : undefined}
>
{item.icon}
{!collapsed && (
<span className="flex-1 text-left">{item.label}</span>
)}
{!collapsed && (
<ChevronLeft
className={cn(
"h-4 w-4 text-gray-400 transition-transform",
expandedSections.includes(item.label) && "-rotate-90",
)}
/>
)}
</button>
{!collapsed && expandedSections.includes(item.label) && (
<div className="ml-4 mt-1 space-y-1">
{renderItems(item.items, depth + 1)}
</div>
)}
</div>
) : (
<a
href={item.href}
className={cn(
"flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-gray-100",
item.active ? "bg-gray-100 text-gray-900" : "text-gray-600",
collapsed && "justify-center",
)}
title={collapsed ? item.label : undefined}
>
{item.icon}
{!collapsed && item.label}
</a>
)}
</div>
));
};
return (
<div
className={cn(
"flex h-full flex-col border-r border-gray-200 bg-white transition-all",
collapsed ? "w-16" : "w-60",
className,
)}
>
<div className="flex items-center justify-end border-b border-gray-200 p-3">
<button
onClick={() => setCollapsed(!collapsed)}
className="rounded-md p-1.5 text-gray-500 hover:bg-gray-100"
>
{collapsed ? (
<Menu className="h-4 w-4" />
) : (
<ChevronLeft className="h-4 w-4" />
)}
</button>
</div>
<nav className="flex-1 space-y-1 overflow-y-auto p-3">
{renderItems(items)}
</nav>
</div>
);
}
CLI
npx genui add sidebar