shadcn.io is not affiliated with official shadcn/ui
Installation
bunx --bun shadcn@latest add https://kit.dev/r/radix/sidebar.jsonnpx shadcn@latest add https://kit.dev/r/radix/sidebar.jsonpnpm dlx shadcn@latest add https://kit.dev/r/radix/sidebar.jsonyarn shadcn@latest add https://kit.dev/r/radix/sidebar.jsonThis component depends on Button, Input, Separator, Sheet, Skeleton, Tooltip and Use Is Mobile. Install them first if you haven't already.
Install the following dependencies:
bun add lucide-react radix-ui tailwind-variantsnpm install lucide-react radix-ui tailwind-variantspnpm add lucide-react radix-ui tailwind-variantsyarn add lucide-react radix-ui tailwind-variantsCopy and paste the following code into your project.
"use client";
import { PanelLeftIcon } from "lucide-react";
import { Slot } from "radix-ui";
import type React from "react";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import { tv, type VariantProps } from "tailwind-variants";
import { cn } from "@/lib/utils";
import { Button } from "@/registry/radix/components/button";
import { Input } from "@/registry/radix/components/input";
import { Separator } from "@/registry/radix/components/separator";Update the import paths to match your project setup.
Anatomy
<SidebarHeader />
<SidebarGroup />
<SidebarGroup />
<SidebarContent />
<SidebarFooter />
<Sidebar />
Usage
import {
AudioWaveformIcon,
BadgeCheckIcon,
BellIcon,
BookOpenIcon,
BotIcon,
ChevronRightIcon,
ChevronsUpDownIcon,
CommandIcon,
CreditCardIcon,
FolderIcon,
ForwardIcon,
FrameIcon,
GalleryVerticalEndIcon,
LogOutIcon,
MapIcon,
MoreHorizontalIcon,
PieChartIcon,
PlusIcon,
Settings2Icon,
SparklesIcon,
SquareTerminalIcon,
Trash2Icon,
} from "lucide-react";
import { type ElementType, useCallback, useState } from "react";
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
Menu,
MenuContent,
MenuGroup,
MenuGroupLabel,
MenuItem,
MenuSeparator,
MenuShortcut,
MenuTrigger,
} from "@/components/ui/menu";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar";
const data = {
navMain: [
{
icon: SquareTerminalIcon,
isActive: true,
items: [
{ title: "History", url: "#" },
{ title: "Starred", url: "#" },
{ title: "Settings", url: "#" },
],
title: "Playground",
url: "#",
},
{
icon: BotIcon,
items: [
{ title: "Genesis", url: "#" },
{ title: "Explorer", url: "#" },
{ title: "Quantum", url: "#" },
],
title: "Models",
url: "#",
},
{
icon: BookOpenIcon,
items: [
{ title: "Introduction", url: "#" },
{ title: "Get Started", url: "#" },
{ title: "Tutorials", url: "#" },
{ title: "Changelog", url: "#" },
],
title: "Documentation",
url: "#",
},
{
icon: Settings2Icon,
items: [
{ title: "General", url: "#" },
{ title: "Team", url: "#" },
{ title: "Billing", url: "#" },
{ title: "Limits", url: "#" },
],
title: "Settings",
url: "#",
},
],
projects: [
{ icon: FrameIcon, name: "Design Engineering", url: "#" },
{ icon: PieChartIcon, name: "Sales & Marketing", url: "#" },
{ icon: MapIcon, name: "Travel", url: "#" },
],
teams: [
{
logo: GalleryVerticalEndIcon,
name: "Acme Inc",
plan: "Enterprise",
},
{
logo: AudioWaveformIcon,
name: "Acme Corp.",
plan: "Startup",
},
{
logo: CommandIcon,
name: "Evil Corp.",
plan: "Free",
},
],
user: {
avatar: "https://github.com/shadcn.png",
email: "[email protected]",
name: "shadcn",
},
};
type Team = (typeof data.teams)[number];
const TeamMenuItem = (props: {
index: number;
onSelect: (team: Team) => void;
team: Team;
}) => {
const { index, onSelect, team } = props;
const handleClick = useCallback(() => {
onSelect(team);
}, [onSelect, team]);
return (
<MenuItem className="gap-2 p-2" onClick={handleClick} value={team.name}>
<div className="flex size-6 items-center justify-center rounded-md border">
<team.logo aria-hidden="true" className="size-3.5 shrink-0" />
</div>
{team.name}
<MenuShortcut>ā{index + 1}</MenuShortcut>
</MenuItem>
);
};
const TeamSwitcher = (props: { teams: Team[] }) => {
const { teams } = props;
const { isMobile } = useSidebar();
const [activeTeam, setActiveTeam] = useState(teams[0]);
const handleSelectTeam = useCallback((team: Team) => {
setActiveTeam(team);
}, []);
if (!activeTeam) {
return null;
}
return (
<SidebarMenu>
<SidebarMenuItem>
<Menu
positioning={{
gutter: 4,
placement: isMobile ? "bottom-start" : "right-start",
}}
>
<MenuTrigger asChild>
<SidebarMenuButton
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
size="lg"
>
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<activeTeam.logo aria-hidden="true" className="size-4" />
</div>
<div className="grid flex-1 text-start text-sm leading-tight">
<span className="truncate font-medium">{activeTeam.name}</span>
<span className="truncate text-xs">{activeTeam.plan}</span>
</div>
<ChevronsUpDownIcon aria-hidden="true" className="ms-auto" />
</SidebarMenuButton>
</MenuTrigger>
<MenuContent className="w-(--reference-width) min-w-56 rounded-lg">
<MenuGroup>
<MenuGroupLabel className="text-muted-foreground text-xs">
Teams
</MenuGroupLabel>
{teams.map((team, index) => (
<TeamMenuItem
index={index}
key={team.name}
onSelect={handleSelectTeam}
team={team}
/>
))}
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem className="gap-2 p-2" value="add-team">
<div className="flex size-6 items-center justify-center rounded-md border bg-transparent">
<PlusIcon aria-hidden="true" className="size-4" />
</div>
<div className="font-medium text-muted-foreground">
Add team
</div>
</MenuItem>
</MenuGroup>
</MenuContent>
</Menu>
</SidebarMenuItem>
</SidebarMenu>
);
};
const NavMain = (props: {
items: {
icon?: ElementType;
isActive?: boolean;
items?: { title: string; url: string }[];
title: string;
url: string;
}[];
}) => {
const { items } = props;
return (
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
<Collapsible
asChild
className="group/collapsible"
defaultOpen={item.isActive}
key={item.title}
lazyMount={false}
unmountOnExit={false}
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{item.icon ? <item.icon aria-hidden="true" /> : null}
<span>{item.title}</span>
<ChevronRightIcon
aria-hidden="true"
className="ms-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
/>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton href={subItem.url}>
<span>{subItem.title}</span>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
))}
</SidebarMenu>
</SidebarGroup>
);
};
const NavProjects = (props: {
projects: { icon: ElementType; name: string; url: string }[];
}) => {
const { projects } = props;
const { isMobile } = useSidebar();
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
{projects.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon aria-hidden="true" />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
<Menu
positioning={{
placement: isMobile ? "bottom-end" : "right-start",
}}
>
<MenuTrigger asChild>
<SidebarMenuAction showOnHover>
<MoreHorizontalIcon aria-hidden="true" />
<span className="sr-only">More</span>
</SidebarMenuAction>
</MenuTrigger>
<MenuContent className="w-48 rounded-lg">
<MenuItem value={`${item.name}-view`}>
<FolderIcon
aria-hidden="true"
className="text-muted-foreground"
/>
<span>View Project</span>
</MenuItem>
<MenuItem value={`${item.name}-share`}>
<ForwardIcon
aria-hidden="true"
className="text-muted-foreground"
/>
<span>Share Project</span>
</MenuItem>
<MenuSeparator />
<MenuItem value={`${item.name}-delete`}>
<Trash2Icon
aria-hidden="true"
className="text-muted-foreground"
/>
<span>Delete Project</span>
</MenuItem>
</MenuContent>
</Menu>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70">
<MoreHorizontalIcon
aria-hidden="true"
className="text-sidebar-foreground/70"
/>
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
);
};
const NavUser = (props: {
user: { avatar: string; email: string; name: string };
}) => {
const { user } = props;
const { isMobile } = useSidebar();
return (
<SidebarMenu>
<SidebarMenuItem>
<Menu
positioning={{
gutter: 4,
placement: isMobile ? "bottom-end" : "right-end",
}}
>
<MenuTrigger asChild>
<SidebarMenuButton
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
size="lg"
>
<Avatar className="size-8 rounded-lg">
<AvatarImage
alt={user.name}
height={32}
src={user.avatar}
width={32}
/>
<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>
<ChevronsUpDownIcon
aria-hidden="true"
className="ms-auto size-4"
/>
</SidebarMenuButton>
</MenuTrigger>
<MenuContent className="w-(--reference-width) min-w-56 rounded-lg">
<MenuGroup>
<MenuGroupLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
<Avatar className="size-8 rounded-lg">
<AvatarImage
alt={user.name}
height={32}
src={user.avatar}
width={32}
/>
<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>
</MenuGroupLabel>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem value="upgrade">
<SparklesIcon aria-hidden="true" />
Upgrade to Pro
</MenuItem>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem value="account">
<BadgeCheckIcon aria-hidden="true" />
Account
</MenuItem>
<MenuItem value="billing">
<CreditCardIcon aria-hidden="true" />
Billing
</MenuItem>
<MenuItem value="notifications">
<BellIcon aria-hidden="true" />
Notifications
</MenuItem>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem value="logout">
<LogOutIcon aria-hidden="true" />
Log out
</MenuItem>
</MenuGroup>
</MenuContent>
</Menu>
</SidebarMenuItem>
</SidebarMenu>
);
};
const SidebarDemo = () => (
<div className="h-[400px] w-full overflow-hidden rounded-xl border">
<SidebarProvider
className="h-full min-h-0"
persist={false}
shortcut={false}
>
<Sidebar className="h-full" collapsible="icon">
<SidebarHeader>
<TeamSwitcher teams={data.teams} />
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavProjects projects={data.projects} />
</SidebarContent>
<SidebarFooter>
<NavUser user={data.user} />
</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">
<div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ms-1" />
</div>
</header>
</SidebarInset>
</SidebarProvider>
</div>
);
Accessibility
| Key | Description |
|---|---|
Enter | Activate the focused control. |
Space | Activate the focused control. |
ArrowDown | Move down. |
ArrowUp | Move up. |
ArrowLeft | Move left. |
ArrowRight | Move right. |
Home | Go to the start. |
End | Go to the end. |
Demos
Rtl
Right-to-left sidebar with static Arabic labels.