Sidebar
Composable application sidebar with collapsible groups, rail, and mobile sheet. Built for app shells.
Examples
#Controlled
#Control the sidebar open state with the SidebarProvider open and onOpenChange props.
import { Button } from "@codefast/ui/button";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
} from "@codefast/ui/sidebar";
import {
FrameIcon,
LifeBuoyIcon,
MapIcon,
PanelLeftCloseIcon,
PanelLeftOpenIcon,
PieChartIcon,
SendIcon,
} from "lucide-react";
import * as React from "react";
const projects = [
{
name: "Design Engineering",
url: "#",
icon: FrameIcon,
},
{
name: "Sales & Marketing",
url: "#",
icon: PieChartIcon,
},
{
name: "Travel",
url: "#",
icon: MapIcon,
},
{
name: "Support",
url: "#",
icon: LifeBuoyIcon,
},
{
name: "Feedback",
url: "#",
icon: SendIcon,
},
];
export function SidebarControlledExample() {
const [open, setOpen] = React.useState(true);
return (
<SidebarProvider
className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full"
open={open}
onOpenChange={setOpen}
>
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{projects.map((project) => (
<SidebarMenuItem key={project.name}>
<SidebarMenuButton asChild>
<a href={project.url}>
<project.icon />
<span>{project.name}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SidebarInset>
<header className="flex h-12 items-center justify-between px-4">
<Button onClick={() => setOpen((open) => !open)} size="sm" variant="ghost">
{open ? <PanelLeftCloseIcon /> : <PanelLeftOpenIcon />}
<span>{open ? "Close" : "Open"} Sidebar</span>
</Button>
</header>
</SidebarInset>
</SidebarProvider>
);
}
Footer
#Add a sticky footer to the sidebar with SidebarFooter — commonly used for a user menu.
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@codefast/ui/dropdown-menu";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from "@codefast/ui/sidebar";
import { ChevronUpIcon } from "lucide-react";
export function SidebarFooterExample() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Sidebar>
<SidebarHeader />
<SidebarContent />
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
Username
<ChevronUpIcon className="ms-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" className="w-(--radix-popper-anchor-width)">
<DropdownMenuItem>
<span>Account</span>
</DropdownMenuItem>
<DropdownMenuItem>
<span>Billing</span>
</DropdownMenuItem>
<DropdownMenuItem>
<span>Sign out</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
<SidebarInset>
<header className="flex h-12 items-center justify-between px-4">
<SidebarTrigger />
</header>
</SidebarInset>
</SidebarProvider>
);
}
Group
#Group related menu items into a section with SidebarGroup and SidebarGroupLabel.
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
} from "@codefast/ui/sidebar";
import { LifeBuoyIcon, SendIcon } from "lucide-react";
export function SidebarGroupExample() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Help</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton>
<LifeBuoyIcon />
Support
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<SendIcon />
Feedback
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
);
}
Group Action
#Add an action button to a section with SidebarGroupAction.
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
} from "@codefast/ui/sidebar";
import { toast, Toaster } from "@codefast/ui/sonner";
import { FrameIcon, MapIcon, PieChartIcon, PlusIcon } from "lucide-react";
export function SidebarGroupActionExample() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Toaster
position="bottom-left"
toastOptions={{
className: "ms-40",
}}
/>
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarGroupAction title="Add Project" onClick={() => toast("You clicked the group action!")}>
<PlusIcon /> <span className="sr-only">Add Project</span>
</SidebarGroupAction>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/">
<FrameIcon />
<span>Design Engineering</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/">
<PieChartIcon />
<span>Sales & Marketing</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/">
<MapIcon />
<span>Travel</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
);
}
Collapsible Group
#Make a SidebarGroup collapsible by wrapping it in a Collapsible component.
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@codefast/ui/collapsible";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
} from "@codefast/ui/sidebar";
import { ChevronDownIcon, LifeBuoyIcon, SendIcon } from "lucide-react";
export function SidebarGroupCollapsibleExample() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Sidebar>
<SidebarContent>
<Collapsible defaultOpen className="group/collapsible">
<SidebarGroup>
<SidebarGroupLabel
asChild
className="text-sm hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
>
<CollapsibleTrigger>
Help
<ChevronDownIcon className="ms-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
</CollapsibleTrigger>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton>
<LifeBuoyIcon />
Support
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<SendIcon />
Feedback
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</CollapsibleContent>
</SidebarGroup>
</Collapsible>
</SidebarContent>
</Sidebar>
</SidebarProvider>
);
}
Header
#Add a sticky header to the sidebar with SidebarHeader — commonly a workspace switcher.
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@codefast/ui/dropdown-menu";
import {
Sidebar,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from "@codefast/ui/sidebar";
import { ChevronDownIcon } from "lucide-react";
export function SidebarHeaderExample() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Sidebar>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
Select Workspace
<ChevronDownIcon className="ms-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-(--radix-popper-anchor-width)">
<DropdownMenuItem>
<span>Acme Inc</span>
</DropdownMenuItem>
<DropdownMenuItem>
<span>Acme Corp.</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
</Sidebar>
<SidebarInset>
<header className="flex h-12 items-center justify-between px-4">
<SidebarTrigger />
</header>
</SidebarInset>
</SidebarProvider>
);
}
Menu
#Render a menu of items inside a group with SidebarMenu and SidebarMenuButton.
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
} from "@codefast/ui/sidebar";
import { FrameIcon, LifeBuoyIcon, MapIcon, PieChartIcon, SendIcon } from "lucide-react";
const projects = [
{
name: "Design Engineering",
url: "#",
icon: FrameIcon,
},
{
name: "Sales & Marketing",
url: "#",
icon: PieChartIcon,
},
{
name: "Travel",
url: "#",
icon: MapIcon,
},
{
name: "Support",
url: "#",
icon: LifeBuoyIcon,
},
{
name: "Feedback",
url: "#",
icon: SendIcon,
},
];
export function SidebarMenuExample() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{projects.map((project) => (
<SidebarMenuItem key={project.name}>
<SidebarMenuButton asChild>
<a href={project.url}>
<project.icon />
<span>{project.name}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
);
}
Menu Action
#Add a secondary action to a menu item with SidebarMenuAction.
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@codefast/ui/dropdown-menu";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
} from "@codefast/ui/sidebar";
import { FrameIcon, LifeBuoyIcon, MapIcon, MoreHorizontalIcon, PieChartIcon, SendIcon } from "lucide-react";
const projects = [
{
name: "Design Engineering",
url: "#",
icon: FrameIcon,
},
{
name: "Sales & Marketing",
url: "#",
icon: PieChartIcon,
},
{
name: "Travel",
url: "#",
icon: MapIcon,
},
{
name: "Support",
url: "#",
icon: LifeBuoyIcon,
},
{
name: "Feedback",
url: "#",
icon: SendIcon,
},
];
export function SidebarMenuActionExample() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{projects.map((project) => (
<SidebarMenuItem key={project.name}>
<SidebarMenuButton asChild className="group-has-[[data-state=open]]/menu-item:bg-sidebar-accent">
<a href={project.url}>
<project.icon />
<span>{project.name}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction>
<MoreHorizontalIcon />
<span className="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent side="right" align="start">
<DropdownMenuItem>
<span>Edit Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
);
}
Menu Badge
#Render a badge inside a menu item with SidebarMenuBadge.
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
} from "@codefast/ui/sidebar";
import { FrameIcon, LifeBuoyIcon, MapIcon, PieChartIcon, SendIcon } from "lucide-react";
const projects = [
{
name: "Design Engineering",
url: "#",
icon: FrameIcon,
badge: "24",
},
{
name: "Sales & Marketing",
url: "#",
icon: PieChartIcon,
badge: "12",
},
{
name: "Travel",
url: "#",
icon: MapIcon,
badge: "3",
},
{
name: "Support",
url: "#",
icon: LifeBuoyIcon,
badge: "21",
},
{
name: "Feedback",
url: "#",
icon: SendIcon,
badge: "8",
},
];
export function SidebarMenuBadgeExample() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{projects.map((project) => (
<SidebarMenuItem key={project.name}>
<SidebarMenuButton asChild className="group-has-[[data-state=open]]/menu-item:bg-sidebar-accent">
<a href={project.url}>
<project.icon />
<span>{project.name}</span>
</a>
</SidebarMenuButton>
<SidebarMenuBadge>{project.badge}</SidebarMenuBadge>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
);
}
Collapsible Menu
#Make a menu item collapsible to reveal a nested submenu.
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@codefast/ui/collapsible";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
} from "@codefast/ui/sidebar";
import { ChevronRightIcon } from "lucide-react";
const items = [
{
title: "Getting Started",
url: "#",
items: [
{
title: "Installation",
url: "#",
},
{
title: "Project Structure",
url: "#",
},
],
},
{
title: "Build Your Application",
url: "#",
items: [
{
title: "Routing",
url: "#",
},
{
title: "Data Fetching",
url: "#",
isActive: true,
},
{
title: "Rendering",
url: "#",
},
{
title: "Caching",
url: "#",
},
{
title: "Styling",
url: "#",
},
{
title: "Optimizing",
url: "#",
},
{
title: "Configuring",
url: "#",
},
{
title: "Testing",
url: "#",
},
{
title: "Authentication",
url: "#",
},
{
title: "Deploying",
url: "#",
},
{
title: "Upgrading",
url: "#",
},
{
title: "Examples",
url: "#",
},
],
},
{
title: "API Reference",
url: "#",
items: [
{
title: "Components",
url: "#",
},
{
title: "File Conventions",
url: "#",
},
{
title: "Functions",
url: "#",
},
{
title: "Config Options",
url: "#",
},
{
title: "CLI",
url: "#",
},
{
title: "Edge Runtime",
url: "#",
},
],
},
{
title: "Architecture",
url: "#",
items: [
{
title: "Accessibility",
url: "#",
},
{
title: "Fast Refresh",
url: "#",
},
{
title: "Build Tooling",
url: "#",
},
{
title: "Supported Browsers",
url: "#",
},
{
title: "Vite",
url: "#",
},
],
},
];
export function SidebarMenuCollapsibleExample() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item, index) => (
<Collapsible key={index} className="group/collapsible" defaultOpen={index === 0}>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton>
<span>{item.title}</span>
<ChevronRightIcon className="ms-auto transition-transform group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items.map((subItem, subIndex) => (
<SidebarMenuSubItem key={subIndex}>
<SidebarMenuSubButton asChild>
<a href={subItem.url}>
<span>{subItem.title}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
);
}
Submenu
#Render a nested submenu inside a menu with SidebarMenuSub.
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
} from "@codefast/ui/sidebar";
const items = [
{
title: "Getting Started",
url: "#",
items: [
{
title: "Installation",
url: "#",
},
{
title: "Project Structure",
url: "#",
},
],
},
{
title: "Build Your Application",
url: "#",
items: [
{
title: "Routing",
url: "#",
},
{
title: "Data Fetching",
url: "#",
isActive: true,
},
{
title: "Rendering",
url: "#",
},
{
title: "Caching",
url: "#",
},
{
title: "Styling",
url: "#",
},
{
title: "Optimizing",
url: "#",
},
{
title: "Configuring",
url: "#",
},
{
title: "Testing",
url: "#",
},
{
title: "Authentication",
url: "#",
},
{
title: "Deploying",
url: "#",
},
{
title: "Upgrading",
url: "#",
},
{
title: "Examples",
url: "#",
},
],
},
{
title: "API Reference",
url: "#",
items: [
{
title: "Components",
url: "#",
},
{
title: "File Conventions",
url: "#",
},
{
title: "Functions",
url: "#",
},
{
title: "Config Options",
url: "#",
},
{
title: "CLI",
url: "#",
},
{
title: "Edge Runtime",
url: "#",
},
],
},
{
title: "Architecture",
url: "#",
items: [
{
title: "Accessibility",
url: "#",
},
{
title: "Fast Refresh",
url: "#",
},
{
title: "Build Tooling",
url: "#",
},
{
title: "Supported Browsers",
url: "#",
},
{
title: "Vite",
url: "#",
},
],
},
];
export function SidebarMenuSubExample() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item, index) => (
<SidebarMenuItem key={index}>
<SidebarMenuButton asChild>
<a href={item.url}>
<span>{item.title}</span>
</a>
</SidebarMenuButton>
<SidebarMenuSub>
{item.items.map((subItem, subIndex) => (
<SidebarMenuSubItem key={subIndex}>
<SidebarMenuSubButton asChild>
<a href={subItem.url}>
<span>{subItem.title}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
);
}
React Server Component
#Stream menu items with a Suspense-style skeleton fallback using SidebarMenuSkeleton.
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarProvider,
} from "@codefast/ui/sidebar";
import { FrameIcon, LifeBuoyIcon, MapIcon, PieChartIcon, SendIcon } from "lucide-react";
import * as React from "react";
const projects = [
{
name: "Design Engineering",
url: "#",
icon: FrameIcon,
badge: "24",
},
{
name: "Sales & Marketing",
url: "#",
icon: PieChartIcon,
badge: "12",
},
{
name: "Travel",
url: "#",
icon: MapIcon,
badge: "3",
},
{
name: "Support",
url: "#",
icon: LifeBuoyIcon,
badge: "21",
},
{
name: "Feedback",
url: "#",
icon: SendIcon,
badge: "8",
},
];
// Dummy fetch simulated on the client so the skeleton is visible in the showcase.
function fetchProjects(): Promise<typeof projects> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(projects);
}, 3000);
});
}
export function SidebarRsc() {
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarGroupContent>
<NavProjects />
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
);
}
function NavProjectsSkeleton() {
return (
<SidebarMenu>
{Array.from({ length: 5 }).map((_, index) => (
<SidebarMenuItem key={index}>
<SidebarMenuSkeleton showIcon />
</SidebarMenuItem>
))}
</SidebarMenu>
);
}
function NavProjects() {
const [data, setData] = React.useState<typeof projects | undefined>(undefined);
React.useEffect(() => {
let active = true;
void fetchProjects().then((result) => {
if (active) {
setData(result);
}
});
return () => {
active = false;
};
}, []);
if (!data) {
return <NavProjectsSkeleton />;
}
return (
<SidebarMenu>
{data.map((project) => (
<SidebarMenuItem key={project.name}>
<SidebarMenuButton asChild>
<a href={project.url}>
<project.icon />
<span>{project.name}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
);
}
RTL
#Right-to-left layout support for languages such as Arabic and Hebrew.
Translations are AI-generated for demonstration and may be imperfect.
import { Avatar, AvatarFallback, AvatarImage } from "@codefast/ui/avatar";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@codefast/ui/collapsible";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@codefast/ui/dropdown-menu";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
useSidebar,
} from "@codefast/ui/sidebar";
import {
BadgeCheck,
Bell,
BookOpen,
Bot,
ChevronRight,
ChevronsUpDown,
CreditCard,
Folder,
Forward,
Frame,
GalleryVerticalEnd,
LogOut,
Map,
MoreHorizontal,
PieChart,
Settings2,
Sparkles,
SquareTerminal,
Trash2,
} from "lucide-react";
import * as React from "react";
import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";
const translations: Translations = {
en: {
dir: "ltr",
values: {
teamName: "Acme Inc",
teamPlan: "Enterprise",
platform: "Platform",
projects: "Projects",
viewProject: "View Project",
shareProject: "Share Project",
deleteProject: "Delete Project",
more: "More",
upgradeToPro: "Upgrade to Pro",
account: "Account",
billing: "Billing",
notifications: "Notifications",
logOut: "Log out",
playground: "Playground",
history: "History",
starred: "Starred",
settings: "Settings",
models: "Models",
genesis: "Genesis",
explorer: "Explorer",
quantum: "Quantum",
documentation: "Documentation",
introduction: "Introduction",
getStarted: "Get Started",
tutorials: "Tutorials",
changelog: "Changelog",
general: "General",
team: "Team",
limits: "Limits",
designEngineering: "Design Engineering",
salesMarketing: "Sales & Marketing",
travel: "Travel",
},
},
ar: {
dir: "rtl",
values: {
teamName: "شركة أكمي",
teamPlan: "المؤسسة",
platform: "المنصة",
projects: "المشاريع",
viewProject: "عرض المشروع",
shareProject: "مشاركة المشروع",
deleteProject: "حذف المشروع",
more: "المزيد",
upgradeToPro: "ترقية إلى Pro",
account: "الحساب",
billing: "الفوترة",
notifications: "الإشعارات",
logOut: "تسجيل الخروج",
playground: "ملعب",
history: "السجل",
starred: "المميز",
settings: "الإعدادات",
models: "النماذج",
genesis: "جينيسيس",
explorer: "إكسبلورر",
quantum: "كوانتوم",
documentation: "التوثيق",
introduction: "مقدمة",
getStarted: "ابدأ",
tutorials: "الدروس",
changelog: "سجل التغييرات",
general: "عام",
team: "الفريق",
limits: "الحدود",
designEngineering: "هندسة التصميم",
salesMarketing: "المبيعات والتسويق",
travel: "السفر",
},
},
he: {
dir: "rtl",
values: {
teamName: "אקמי בע״מ",
teamPlan: "ארגוני",
platform: "פלטפורמה",
projects: "פרויקטים",
viewProject: "הצג פרויקט",
shareProject: "שתף פרויקט",
deleteProject: "מחק פרויקט",
more: "עוד",
upgradeToPro: "שדרג ל-Pro",
account: "חשבון",
billing: "חיוב",
notifications: "התראות",
logOut: "התנתק",
playground: "מגרש משחקים",
history: "היסטוריה",
starred: "מועדפים",
settings: "הגדרות",
models: "מודלים",
genesis: "ג'נסיס",
explorer: "אקספלורר",
quantum: "קוונטום",
documentation: "תיעוד",
introduction: "מבוא",
getStarted: "התחל",
tutorials: "מדריכים",
changelog: "יומן שינויים",
general: "כללי",
team: "צוות",
limits: "מגבלות",
designEngineering: "הנדסת עיצוב",
salesMarketing: "מכירות ושיווק",
travel: "נסיעות",
},
},
};
export function SidebarRtl() {
const { dir, t } = useTranslation(translations, "ar");
const navMain = [
{
title: t.playground ?? "",
url: "#",
icon: SquareTerminal,
isActive: true,
items: [
{ title: t.history ?? "", url: "#" },
{ title: t.starred ?? "", url: "#" },
{ title: t.settings ?? "", url: "#" },
],
},
{
title: t.models ?? "",
url: "#",
icon: Bot,
items: [
{ title: t.genesis ?? "", url: "#" },
{ title: t.explorer ?? "", url: "#" },
{ title: t.quantum ?? "", url: "#" },
],
},
{
title: t.documentation ?? "",
url: "#",
icon: BookOpen,
items: [
{ title: t.introduction ?? "", url: "#" },
{ title: t.getStarted ?? "", url: "#" },
{ title: t.tutorials ?? "", url: "#" },
{ title: t.changelog ?? "", url: "#" },
],
},
{
title: t.settings ?? "",
url: "#",
icon: Settings2,
items: [
{ title: t.general ?? "", url: "#" },
{ title: t.team ?? "", url: "#" },
{ title: t.billing ?? "", url: "#" },
{ title: t.limits ?? "", url: "#" },
],
},
];
const projects = [
{ name: t.designEngineering ?? "", url: "#", icon: Frame },
{ name: t.salesMarketing ?? "", url: "#", icon: PieChart },
{ name: t.travel ?? "", url: "#", icon: Map },
];
const user = {
name: "codefast",
email: "m@example.com",
avatar: "https://github.com/codefastlabs.png",
};
return (
<SidebarProvider className="h-96 min-h-0 w-full overflow-hidden rounded-xl border [contain:layout] [&_[data-slot=sidebar-container]]:h-full">
<SidebarContentInner dir={dir} t={t} navMain={navMain} projects={projects} user={user} />
</SidebarProvider>
);
}
function SidebarContentInner({
dir,
t,
navMain,
projects,
user,
}: {
dir: "ltr" | "rtl";
t: typeof translations.ar.values;
navMain: Array<{
title: string;
url: string;
icon?: React.ElementType;
isActive?: boolean;
items?: Array<{ title: string; url: string }>;
}>;
projects: Array<{
name: string;
url: string;
icon: React.ElementType;
}>;
user: {
name: string;
email: string;
avatar: string;
};
}) {
const { isMobile } = useSidebar("AppSidebar");
return (
<>
<Sidebar collapsible="icon" dir={dir} side={dir === "ltr" ? "left" : "right"}>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" asChild>
<a href="/">
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<GalleryVerticalEnd className="size-4" />
</div>
<div className="flex flex-col gap-0.5 leading-none">
<span className="font-medium">{t.teamName}</span>
<span className="">{t.teamPlan}</span>
</div>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>{t.platform}</SidebarGroupLabel>
<SidebarMenu>
{navMain.map((item) => (
<Collapsible
key={item.title}
asChild
defaultOpen={item.isActive ?? false}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{item.icon && <item.icon />}
<span>{item.title}</span>
<ChevronRight className="ms-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton asChild>
<a href={subItem.url}>
<span>{subItem.title}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
))}
</SidebarMenu>
</SidebarGroup>
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>{t.projects}</SidebarGroupLabel>
<SidebarMenu>
{projects.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction showOnHover>
<MoreHorizontal />
<span className="sr-only">{t.more}</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-48 rounded-lg"
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}
>
<DropdownMenuGroup>
<DropdownMenuItem>
<Folder className="text-muted-foreground" />
<span>{t.viewProject}</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Forward className="text-muted-foreground" />
<span>{t.shareProject}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Trash2 className="text-muted-foreground" />
<span>{t.deleteProject}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70">
<MoreHorizontal className="text-sidebar-foreground/70" />
<span>{t.more}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar className="rounded-lg">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-start text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
<ChevronsUpDown className="ms-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
align="end"
sideOffset={4}
>
<DropdownMenuGroup>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
<Avatar>
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-start text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
</div>
</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Sparkles />
{t.upgradeToPro}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<BadgeCheck />
{t.account}
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard />
{t.billing}
</DropdownMenuItem>
<DropdownMenuItem>
<Bell />
{t.notifications}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<LogOut />
{t.logOut}
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header
className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12"
dir={dir}
>
<div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ms-1" />
</div>
</header>
</SidebarInset>
</>
);
}
Anatomy
#How the parts nest — every slot the component exposes, in composition order.
Features
#- Persists the open/collapsed state in a sidebar_state cookie (7-day max-age), so a full page reload keeps the sidebar as the user left it.
- Automatically switches to a Sheet-based drawer on mobile via useIsMobile() — no separate mobile layout to build.
API reference
#Props for each part of the component. All native element props are also forwarded.
SidebarProvider
Wraps the shell; owns collapse state and keyboard shortcut.
defaultOpenbooleanWhether the sidebar starts expanded.
Default
trueopenbooleanThe controlled open (expanded) state.
onOpenChange(open: boolean) => voidCalled when the open state changes.
Sidebar parts
SidebarMenuButtonReactNodeA nav row; compose inside SidebarMenuItem / SidebarGroup.
SidebarTrigger—Toggles the sidebar; place it in your main content.
Accessibility
#Built to be keyboard-navigable and screen-reader friendly out of the box.
| Key | Function |
|---|---|
| ⌘/Ctrl+B | Toggles the sidebar from anywhere in the app. |
| Tab | Moves through the menu buttons. |
| Enter | Activates the focused item. |
- Built as a nav landmark; the collapsed rail keeps icons reachable.
- On mobile it becomes a sheet — ensure the trigger is always reachable.
- Give icon-only collapsed items accessible names (tooltips or labels).
Guidelines
#Conventions that keep usage consistent across an app.
Do
- Use for app shells with persistent navigation.
- Group links with labels and keep the list shallow.
Don’t
- Don’t use a sidebar for a content site with few pages.
- Don’t hide the toggle — users need a way back.
Related
#Related components
Explore further
Ready to integrate?
Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.