lumiui
blocksidebar-01source: ok · hit
Sidebar 01
Sidebar with collapsible icons and sections.
npx shadcn add "https://www.lumiui.dev/r/sidebar-01.json"free
Where it goes
sidebardashboard-shell
Interaction
accordion
Use cases
dashboard
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightRegistry dependencies
@lumi-ui/sidebar@lumi-ui/separator@lumi-ui/scroll-area@lumi-ui/dropdown-menu@lumi-ui/dialog@lumi-ui/chart@lumi-ui/tabs@lumi-ui/input@lumi-ui/sheet@lumi-ui/form@lumi-ui/field@lumi-ui/button@lumi-ui/combobox@lumi-ui/collapsible@lumi-ui/toggle-group@lumi-ui/toast@lumi-ui/card@lumi-ui/breadcrumb@lumi-ui/kbd
Files7 · source cached 10/8/2026, 8:16:07 PM
registry/blocks/sidebar-01/page.tsx
import { cookies } from "next/headers";
import type { Layout } from "react-resizable-panels";
import { AppSidebar } from "@/registry/blocks/sidebar-01/components/app-sidebar";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/registry/ui/breadcrumb";
import { Separator } from "@/registry/ui/separator";
import {
MainContent,
SidebarProvider,
SidebarTrigger,
} from "@/registry/ui/sidebar";
const GROUP_ID = "main-layout-persistence";
export default async function Page() {
const cookieStore = await cookies();
const layoutCookie = cookieStore.get(GROUP_ID);
const defaultLayout: Layout = layoutCookie
? JSON.parse(layoutCookie.value)
: undefined;
return (
<SidebarProvider
collapsibleType="icon"
defaultLayout={defaultLayout}
groupId={GROUP_ID}
>
<AppSidebar />
<MainContent className="flex flex-col">
<header className="flex h-12 bg-background z-10 sticky top-0 items-center gap-2 p-2 sm:p-4">
<SidebarTrigger />
<Separator className="mr-2 h-4" orientation="vertical" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">Dashboard</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>Financial Performance</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</header>
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="bg-muted/50 aspect-video rounded-xl" />
<div className="bg-muted/50 aspect-video rounded-xl" />
<div className="bg-muted/50 aspect-video rounded-xl" />
</div>
<div className="bg-muted/50 min-h-[100vh] flex-1 rounded-xl md:min-h-min" />
</div>
</MainContent>
</SidebarProvider>
);
}
registry/blocks/sidebar-01/components/app-sidebar.tsx
"use client";
import {
AudioWaveform,
BookOpen,
Bot,
Command,
Frame,
GalleryVerticalEnd,
MapIcon,
PieChart,
Settings2,
SquareTerminal,
} from "lucide-react";
import type * as React from "react";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarSeparator,
} from "@/registry/ui/sidebar";
import { NavMain } from "./nav-main";
import { NavProjects } from "./nav-projects";
import { NavUser } from "./nav-user";
import { SearchCombobox } from "./search-combobox";
import { TeamSwitcher } from "./team-switcher";
// This is sample data.
const data = {
navMain: [
{
icon: SquareTerminal,
isActive: true,
items: [
{
title: "History",
url: "#",
},
{
title: "Starred",
url: "#",
},
{
title: "Settings",
url: "#",
},
],
title: "Playground",
url: "#",
},
{
icon: Bot,
items: [
{
title: "Genesis",
url: "#",
},
{
title: "Explorer",
url: "#",
},
{
title: "Quantum",
url: "#",
},
],
title: "Models",
url: "#",
},
{
icon: BookOpen,
items: [
{
title: "Introduction",
url: "#",
},
{
title: "Get Started",
url: "#",
},
{
title: "Tutorials",
url: "#",
},
{
title: "Changelog",
url: "#",
},
],
title: "Documentation",
url: "#",
},
{
icon: Settings2,
items: [
{
title: "General",
url: "#",
},
{
title: "Team",
url: "#",
},
{
title: "Billing",
url: "#",
},
{
title: "Limits",
url: "#",
},
],
title: "Settings",
url: "#",
},
],
projects: [
{
icon: Frame,
name: "Design Engineering",
url: "#",
},
{
icon: PieChart,
name: "Sales & Marketing",
url: "#",
},
{
icon: MapIcon,
name: "Travel",
url: "#",
},
],
teams: [
{
logo: GalleryVerticalEnd,
name: "Acme Inc",
plan: "Enterprise",
},
{
logo: AudioWaveform,
name: "Acme Corp.",
plan: "Startup",
},
{
logo: Command,
name: "Evil Corp.",
plan: "Free",
},
],
user: {
email: "m@example.com",
name: "Lumi UI",
},
};
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar {...props}>
<SidebarHeader>
<TeamSwitcher />
<SearchCombobox />
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavProjects projects={data.projects} />
</SidebarContent>
<SidebarSeparator className="mx-0" />
<SidebarFooter>
<NavUser user={data.user} />
</SidebarFooter>
</Sidebar>
);
}
registry/blocks/sidebar-01/components/nav-main.tsx
"use client";
import { ChevronRight, type LucideIcon } from "lucide-react";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/registry/ui/collapsible";
import {
SidebarGroup,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
} from "@/registry/ui/sidebar";
export function NavMain({
items,
}: {
items: {
title: string;
url: string;
icon?: LucideIcon;
isActive?: boolean;
items?: {
title: string;
url: string;
}[];
}[];
}) {
return (
<SidebarGroup>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<Collapsible defaultOpen={item.isActive}>
<CollapsibleTrigger
className="group/collapsible data-[panel-open]:bg-sidebar-accent data-[panel-open]:text-sidebar-accent-foreground"
render={
<SidebarMenuButton
tooltip={{
children: item.title,
side: "right",
}}
>
{item.icon && <item.icon />}
<span>{item.title}</span>
<ChevronRight className="ml-auto text-muted-foreground transition-[transform,colors] duration-200 group-data-[panel-open]/collapsible:rotate-90 group-data-[panel-open]/collapsible:text-foreground group-hover/collapsible:text-sidebar-foreground" />
</SidebarMenuButton>
}
/>
<CollapsiblePanel>
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton
render={
<a href={subItem.url}>
<span>{subItem.title}</span>
</a>
}
/>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsiblePanel>
</Collapsible>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
);
}
registry/blocks/sidebar-01/components/nav-projects.tsx
"use client";
import {
Folder,
Forward,
type LucideIcon,
MoreHorizontal,
Trash2,
} from "lucide-react";
import { Button } from "@/registry/ui/button";
import {
createDialogHandle,
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/registry/ui/dialog";
import {
createDropdownMenuHandle,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuGroupLabel,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/ui/dropdown-menu";
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/registry/ui/sidebar";
import { toast } from "@/registry/ui/toast";
interface Project {
name: string;
url: string;
icon: LucideIcon;
}
const projectMenuHandle = createDropdownMenuHandle<Project>();
const deleteProjectDialogHandle = createDialogHandle<Project>();
export function NavProjects({ projects }: { projects: Project[] }) {
const { isMobile } = useSidebar();
return (
<>
<SidebarGroup className="group-data-[state=collapsed]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
{projects.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton
render={
<a href={item.url}>
<item.icon />
<span>{item.name}</span>
<SidebarMenuAction
render={
<DropdownMenuTrigger
handle={projectMenuHandle}
id={item.name}
payload={item}
/>
}
showOnHover
>
<MoreHorizontal className="text-sidebar-foreground/70" />
</SidebarMenuAction>
</a>
}
></SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
<DropdownMenu handle={projectMenuHandle}>
{({ payload: project }) => {
return (
<DropdownMenuContent
align={isMobile ? "end" : "start"}
className="w-48 rounded-lg"
side={isMobile ? "bottom" : "right"}
>
<DropdownMenuGroup>
<DropdownMenuGroupLabel>{project?.name}</DropdownMenuGroupLabel>
<DropdownMenuItem>
<Folder className="text-muted-foreground" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Forward className="text-muted-foreground" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
if (project) {
deleteProjectDialogHandle.openWithPayload(project);
}
}}
variant={"destructive"}
>
<Trash2 className="text-muted-foreground" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
);
}}
</DropdownMenu>
<Dialog handle={deleteProjectDialogHandle}>
{({ payload: project }) => {
return (
<DialogContent layout="center">
<DialogHeader>
<DialogTitle>
Delete
<span className="ml-2 text-destructive/80">
{project?.name}
</span>
</DialogTitle>
<DialogDescription>
Are you sure you want to delete this project? This action
cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose
render={<Button variant="outline">Cancel</Button>}
/>
<DialogClose
onClick={() =>
toast.promise(
new Promise<void>((resolve, reject) => {
setTimeout(() => {
const shouldSucceed = Math.random() > 0.1;
if (shouldSucceed) {
resolve();
} else {
reject(
new Error(`Failed to delete ${project?.name}.`),
);
}
}, 1500);
}),
{
error: (err: Error) => {
return { description: err.message, title: "Error" };
},
loading: { title: `Deleting ${project?.name}...` },
success: () => {
return {
description: "Drag/swipe to dismiss",
title: `${project?.name} deleted`,
};
},
},
)
}
render={<Button variant="destructive">Delete</Button>}
/>
</DialogFooter>
</DialogContent>
);
}}
</Dialog>
</>
);
}
registry/blocks/sidebar-01/components/nav-user.tsx
"use client";
import {
BookIcon,
ChevronsUpDown,
HouseIcon,
LogOut,
PenIcon,
Settings,
SmileIcon,
UserIcon,
} from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/ui/dropdown-menu";
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/registry/ui/sidebar";
export function NavUser({
user,
}: {
user: {
name: string;
email: string;
};
}) {
const { isCollapsed } = useSidebar();
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger
render={
<SidebarMenuButton
className="data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground"
size="lg"
>
{isCollapsed ? (
<UserIcon className="size-4 mx-auto" />
) : (
<>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
<ChevronsUpDown className="ml-auto size-4" />
</>
)}
</SidebarMenuButton>
}
/>
<DropdownMenuContent
align="start"
matchAnchorWidth={!isCollapsed}
sideOffset={4}
>
<DropdownMenuItem className="justify-between">
<div className="grid text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
<Settings />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem className="justify-between py-2">
Feedback
<SmileIcon />
</DropdownMenuItem>
<DropdownMenuItem className="justify-between py-2">
Homepage
<HouseIcon />
</DropdownMenuItem>
<DropdownMenuItem className="justify-between py-2">
Changelog
<PenIcon />
</DropdownMenuItem>
<DropdownMenuItem className="justify-between py-2">
Doc
<BookIcon />
</DropdownMenuItem>
<DropdownMenuItem className="justify-between py-2">
Log out
<LogOut />
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}
registry/blocks/sidebar-01/components/search-combobox.tsx
"use client";
import {
CpuIcon,
EyeIcon,
SearchIcon,
Settings2Icon,
SparklesIcon,
TvMinimalIcon,
UserIcon,
} from "lucide-react";
import { useState } from "react";
import { cn } from "@/registry/lib/utils";
import { Button } from "@/registry/ui/button";
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInputGroupContent,
ComboboxItemContent,
ComboboxList,
ComboboxTrigger,
} from "@/registry/ui/combobox";
import { Kbd } from "@/registry/ui/kbd";
import { Separator } from "@/registry/ui/separator";
import { SidebarMenuButton, useSidebar } from "@/registry/ui/sidebar";
export function SearchCombobox() {
const [open, setOpen] = useState(false);
const { isCollapsed } = useSidebar();
return (
<Combobox
defaultValue={teamMembers[0]}
items={teamMembers}
onOpenChange={setOpen}
open={open}
>
<ComboboxTrigger
render={
<SidebarMenuButton
className={cn("cursor-text", !isCollapsed && "border")}
tooltip={{
children: "Search",
}}
>
<SearchIcon className="size-4" />
<span className="hidden flex-1 text-left md:flex text-sm">
Find..
</span>
</SidebarMenuButton>
}
/>
<ComboboxContent
align="start"
className={cn(isCollapsed && "w-64")}
matchAnchorWidth={!isCollapsed}
side={isCollapsed ? "right" : "bottom"}
sideOffset={isCollapsed ? 2 : -32}
>
<div className="flex justify-between items-center relative">
<ComboboxInputGroupContent
addonIcon={<SearchIcon />}
inputSize="lg"
placeholder="Find..."
variant="ghost"
/>
<Button
className="absolute hidden lg:block right-2 rounded-sm text-muted-foreground cursor-pointer"
onClick={() => setOpen(false)}
size="icon"
variant="unstyled"
>
<Kbd className="bg-accent">Esc</Kbd>
</Button>
</div>
<Separator />
<ComboboxEmpty>No member found.</ComboboxEmpty>
<ComboboxList>
{(member: TeamMember) => (
<ComboboxItemContent
className="py-2 cursor-pointer"
indicatorIcon={null}
indicatorPlacement="none"
key={member.id}
render={
<a className="inline-flex w-full" href="#">
<span>{IconMap[member.iconName]}</span>
<span className="flex flex-col ml-2 flex-1">
{member.label}
<span className="text-xs text-muted-foreground truncate">
{member.description}
</span>
</span>
</a>
}
value={member}
/>
)}
</ComboboxList>
<Separator />
</ComboboxContent>
</Combobox>
);
}
interface TeamMember {
id: string;
value: string;
description: string;
label: string;
iconName: IconName;
}
type IconName = keyof typeof IconMap;
const IconMap = {
cpu: <CpuIcon className="size-4" />,
eye: <EyeIcon className="size-4" />,
settings: <Settings2Icon className="size-4" />,
sparkles: <SparklesIcon className="size-4" />,
tv: <TvMinimalIcon className="size-4" />,
user: <UserIcon className="size-4" />,
} as const;
const teamMembers: TeamMember[] = [
{
description: "Run apps safely",
iconName: "cpu",
id: "1",
label: "Sandboxes",
value: "sandboxes",
},
{
description: "View usage",
iconName: "eye",
id: "2",
label: "Observability",
value: "observability",
},
{
description: "Explore AI models",
iconName: "tv",
id: "3",
label: "AI Gateway",
value: "ai-gateway",
},
{
description: "Build and Deployment",
iconName: "settings",
id: "4",
label: "Build Machines",
value: "build-machines",
},
{
description: "what did we deploy today?",
iconName: "sparkles",
id: "5",
label: "Navigation Assistant",
value: "navigation-assistant",
},
];
registry/blocks/sidebar-01/components/team-switcher.tsx
"use client";
import { ChevronsUpDown, PlusIcon, UserSearch } from "lucide-react";
import { useState } from "react";
import { cn } from "@/registry/lib/utils";
import { Button } from "@/registry/ui/button";
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInputGroupContent,
ComboboxItemContent,
ComboboxList,
ComboboxTrigger,
ComboboxValue,
} from "@/registry/ui/combobox";
import { Field, FieldError, FieldLabel } from "@/registry/ui/field";
import { Form } from "@/registry/ui/form";
import { Input } from "@/registry/ui/input";
import { Kbd } from "@/registry/ui/kbd";
import { Separator } from "@/registry/ui/separator";
import {
createSheetHandle,
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/registry/ui/sheet";
import { SidebarMenuButton, useSidebar } from "@/registry/ui/sidebar";
import { toast } from "@/registry/ui/toast";
const sheetHandle = createSheetHandle();
export function TeamSwitcher() {
const [comboboxOpen, setComboboxOpen] = useState(false);
const { isCollapsed } = useSidebar();
return (
<>
<Combobox
defaultValue={teamMembers[0]}
items={teamMembers}
onOpenChange={setComboboxOpen}
open={comboboxOpen}
>
<ComboboxTrigger
render={
<SidebarMenuButton className="data-[popup-open]:bg-sidebar-accent data-[popup-open]:text-sidebar-accent-foreground">
<div
className={cn(
"grid flex-1 text-left text-sm leading-tight",
isCollapsed && "hidden",
)}
>
<span className="truncate font-semibold">
<ComboboxValue />
</span>
</div>
{isCollapsed ? (
<UserSearch className="mx-auto" />
) : (
<ChevronsUpDown className="ml-auto" />
)}
</SidebarMenuButton>
}
/>
<ComboboxContent
matchAnchorWidth={!isCollapsed}
side={isCollapsed ? "right" : "bottom"}
>
<div className="flex justify-between items-center relative">
<ComboboxInputGroupContent
placeholder="Find member..."
variant="ghost"
/>
<Button
className="absolute hidden lg:block right-2 rounded-sm text-muted-foreground cursor-pointer"
onClick={() => setComboboxOpen(false)}
size="icon"
variant="unstyled"
>
<Kbd className="bg-accent">Esc</Kbd>
</Button>
</div>
<Separator />
<ComboboxEmpty>No member found.</ComboboxEmpty>
<ComboboxList>
{(member: TeamMember) => (
<ComboboxItemContent
indicatorPlacement="end"
key={member.id}
value={member}
>
{member.label}
</ComboboxItemContent>
)}
</ComboboxList>
<Separator />
<div className="p-2">
<SheetTrigger
handle={sheetHandle}
onClick={() => setComboboxOpen(false)}
render={
<Button className="my-1 w-full" size="sm">
<PlusIcon /> Create team
</Button>
}
/>
</div>
</ComboboxContent>
</Combobox>
<Sheet handle={sheetHandle}>
<SheetContent inset side="right">
<SheetHeader>
<SheetTitle>New Team</SheetTitle>
<SheetDescription>
Unlock collaboration and improved performance.
</SheetDescription>
</SheetHeader>
<Form
className="h-full justify-between"
onFormSubmit={(values) => {
toast.success({
title: `${values.team} created!`,
});
sheetHandle.close();
}}
>
<div className="flex gap-4 flex-col">
<Field name="team">
<FieldLabel>Team Name</FieldLabel>
<Input placeholder="acme" required type="text" />
<FieldError />
</Field>
<Field name="description">
<FieldLabel>Description</FieldLabel>
<Input placeholder="team description" />
</Field>
</div>
<SheetFooter>
<Button type="submit">Save</Button>
<SheetClose render={<Button variant="ghost" />}>
Cancel
</SheetClose>
</SheetFooter>
</Form>
</SheetContent>
</Sheet>
</>
);
}
interface TeamMember {
id: string;
value: string;
role: string;
label: string;
}
const teamMembers: TeamMember[] = [
{ id: "1", label: "Alex Davis", role: "Product", value: "alex.d" },
{ id: "2", label: "Sarah King", role: "Engineering", value: "sarah.k" },
{ id: "3", label: "James Wilson", role: "Design", value: "james.w" },
{ id: "4", label: "Maria Garcia", role: "Marketing", value: "maria.g" },
{ id: "5", label: "David Chen", role: "Engineering", value: "david.c" },
{ id: "6", label: "Emma Roberts", role: "Product", value: "emma.r" },
];