Rendered from the shared Frontfriend stories with React Base, the same
preview Studio shows. Styling comes from your Design System; the code below
is native to Base UI.
Installation
Complete project setup first. Your configuration
must select react/base and include your Design System ID.
pnpm exec frontfriend download sidebar -f react --primitive base
Review dependencies reported by the downloader and keep your source
changes in version control.
Usage
import {
SidebarProvider,
useSidebar,
Sidebar,
SidebarHeader,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarMenuSub,
SidebarMenuSubItem,
SidebarMenuSubButton,
SidebarMenuAction,
SidebarFooter,
SidebarRail,
SidebarInset,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { useEffect, useState } from "react";
import type { IconName } from "@/components/ui/icon";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuShortcut,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Icon } from "@/components/ui/icon";
import {
Collapsible,
CollapsibleTrigger,
CollapsibleContent,
} from "@/components/ui/collapsible";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import * as React from "react";
interface SidebarExampleProps {
open?: boolean;
}
interface AppSidebarProps {
open?: boolean;
collapsible?: "icon" | "offcanvas" | "none";
}
interface UserData {
name: string;
email: string;
avatar: string;
}
interface TeamData {
name: string;
logo: IconName;
plan: string;
}
interface AppNavItem {
title: string;
icon: IconName;
isActive?: boolean;
items?: {
title: string;
url?: string;
}[];
}
interface ProjectData {
name: string;
icon: IconName;
}
interface AppData {
user: UserData;
teams: TeamData[];
navMain: AppNavItem[];
projects: ProjectData[];
}
interface Team {
name: string;
logo: IconName;
plan: string;
}
interface TeamSwitcherProps {
teams: Team[];
}
function TeamSwitcher({ teams }: TeamSwitcherProps) {
const { isMobile } = useSidebar();
const [activeTeam, setActiveTeam] = useState(teams[0]);
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton size="lg">
<div className="rounded-lg p-2 bg-brand-strong">
<Icon name="Compass" className="text-white" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
<span className="truncate font-semibold">
{activeTeam.name}
</span>
<span className="truncate text-xs">{activeTeam.plan}</span>
</div>
<Icon
name="ChevronDown"
className="ml-auto group-data-[collapsible=icon]:hidden"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--reka-dropdownmenu-trigger-width] min-w-56 rounded-lg z-[51]"
align="start"
side={isMobile ? "bottom" : "right"}
sideOffset={4}
>
<DropdownMenuGroup>
<DropdownMenuLabel className="text-xs text-neutral-subtle">
Teams
</DropdownMenuLabel>
</DropdownMenuGroup>
{teams.map((team, index) => (
<DropdownMenuItem
key={team.name}
className="gap-2 p-2"
onClick={() => setActiveTeam(team)}
>
<div className="flex size-6 items-center justify-center rounded-sm border">
<Icon name={team.logo} className="size-4 shrink-0" />
</div>
{team.name}
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-2 p-2">
<div className="flex size-6 items-center justify-center rounded-md border bg-layer-below">
<Icon name="Plus" className="size-4" />
</div>
<div className="font-medium text-neutral-subtle">Add team</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}
interface NavItem {
title: string;
url?: string;
icon?: IconName;
isActive?: boolean;
items?: {
title: string;
url?: string;
}[];
}
interface NavMainProps {
items: NavItem[];
}
function NavMain({ items }: NavMainProps) {
return (
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
<Collapsible
key={item.title}
defaultOpen={item.isActive}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{item.icon && <Icon name={item.icon} />}
<span className="group-data-[collapsible=icon]:hidden">
{item.title}
</span>
<Icon
name="ChevronRight"
className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90 group-data-[collapsible=icon]:hidden"
/>
</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>
);
}
interface Project {
name: string;
url?: string;
icon: IconName;
}
interface NavProjectsProps {
projects: Project[];
}
function NavProjects({ projects }: NavProjectsProps) {
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}>
<Icon name={item.icon} />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction showOnHover>
<Icon name="EllipsisVertical" color="neutral-mid" />
<span className="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-48 rounded-lg z-[51]"
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}
>
<DropdownMenuItem>
<Icon name="Folder" className="text-neutral-subtle" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="ArrowRight" className="text-neutral-subtle" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="Trash" className="text-neutral-subtle" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
);
}
interface User {
name: string;
email: string;
avatar: string;
}
interface NavUserProps {
user: User;
}
function NavUser({ user }: NavUserProps) {
const { isMobile } = useSidebar();
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-neutral-strong data-[state=open]:text-neutral-subtle"
>
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
<span className="truncate font-semibold">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
<Icon
name="ChevronDown"
className="ml-auto size-4 group-data-[collapsible=icon]:hidden"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-full min-w-56 rounded-lg z-[51]"
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-left text-sm">
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
</div>
</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="Sparkles" />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="BadgeCheck" />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="CreditCard" />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="Bell" />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="LogOut" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}
function AppSidebar({ open, collapsible = "icon" }: AppSidebarProps) {
const { setOpen } = useSidebar();
useEffect(() => {
if (open !== undefined) {
setOpen(open);
}
}, [open]);
const data: AppData = {
user: {
name: "Gustavo",
email: "g.miro@example.com",
avatar:
"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2248%22 height=%2248%22%3E%3Crect width=%2248%22 height=%2248%22 rx=%2224%22 fill=%22%236366f1%22/%3E%3C/svg%3E",
},
teams: [
{
name: "Acme Inc",
logo: "GalleryVerticalEnd",
plan: "Startup",
},
{
name: "Acme Corp.",
logo: "AudioWaveform",
plan: "Startup",
},
{
name: "Evil Corp.",
logo: "Command",
plan: "Free",
},
],
navMain: [
{
title: "Playground",
icon: "SquareCode",
isActive: true,
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
{
title: "Models",
icon: "Bot",
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
{
title: "Documentation",
icon: "BookOpen",
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
{
title: "Settings",
icon: "Settings2",
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
],
projects: [
{
name: "Design Engineering",
icon: "Frame",
},
{
name: "Sales & Marketing",
icon: "ChartPie",
},
{
name: "Development",
icon: "Code",
},
{
name: "Advertisement",
icon: "Radio",
},
],
};
return (
<Sidebar collapsible={collapsible} className="h-full min-h-0">
<SidebarHeader>
<TeamSwitcher teams={data.teams} />
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavProjects projects={data.projects} />
</SidebarContent>
<SidebarFooter>
<NavUser user={data.user} />
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
}
function SidebarExample({ open }: SidebarExampleProps) {
const [expanded, setExpanded] = React.useState(open ?? true);
return (
<div className="h-[560px] w-[900px] max-w-full relative overflow-hidden [transform:translateZ(0)]">
<SidebarProvider
open={expanded}
onOpenChange={setExpanded}
className="min-h-0 h-full"
>
<AppSidebar />
<SidebarInset className="min-h-0 h-full">
<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="-ml-1" />
</div>
</header>
</SidebarInset>
</SidebarProvider>
</div>
);
}
<SidebarExample open />;
Examples
Main
import {
SidebarProvider,
useSidebar,
Sidebar,
SidebarHeader,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarMenuSub,
SidebarMenuSubItem,
SidebarMenuSubButton,
SidebarMenuAction,
SidebarFooter,
SidebarRail,
SidebarInset,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { useEffect, useState } from "react";
import type { IconName } from "@/components/ui/icon";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuShortcut,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Icon } from "@/components/ui/icon";
import {
Collapsible,
CollapsibleTrigger,
CollapsibleContent,
} from "@/components/ui/collapsible";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import * as React from "react";
interface SidebarExampleProps {
open?: boolean;
}
interface AppSidebarProps {
open?: boolean;
collapsible?: "icon" | "offcanvas" | "none";
}
interface UserData {
name: string;
email: string;
avatar: string;
}
interface TeamData {
name: string;
logo: IconName;
plan: string;
}
interface AppNavItem {
title: string;
icon: IconName;
isActive?: boolean;
items?: {
title: string;
url?: string;
}[];
}
interface ProjectData {
name: string;
icon: IconName;
}
interface AppData {
user: UserData;
teams: TeamData[];
navMain: AppNavItem[];
projects: ProjectData[];
}
interface Team {
name: string;
logo: IconName;
plan: string;
}
interface TeamSwitcherProps {
teams: Team[];
}
function TeamSwitcher({ teams }: TeamSwitcherProps) {
const { isMobile } = useSidebar();
const [activeTeam, setActiveTeam] = useState(teams[0]);
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton size="lg">
<div className="rounded-lg p-2 bg-brand-strong">
<Icon name="Compass" className="text-white" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
<span className="truncate font-semibold">
{activeTeam.name}
</span>
<span className="truncate text-xs">{activeTeam.plan}</span>
</div>
<Icon
name="ChevronDown"
className="ml-auto group-data-[collapsible=icon]:hidden"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--reka-dropdownmenu-trigger-width] min-w-56 rounded-lg z-[51]"
align="start"
side={isMobile ? "bottom" : "right"}
sideOffset={4}
>
<DropdownMenuGroup>
<DropdownMenuLabel className="text-xs text-neutral-subtle">
Teams
</DropdownMenuLabel>
</DropdownMenuGroup>
{teams.map((team, index) => (
<DropdownMenuItem
key={team.name}
className="gap-2 p-2"
onClick={() => setActiveTeam(team)}
>
<div className="flex size-6 items-center justify-center rounded-sm border">
<Icon name={team.logo} className="size-4 shrink-0" />
</div>
{team.name}
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-2 p-2">
<div className="flex size-6 items-center justify-center rounded-md border bg-layer-below">
<Icon name="Plus" className="size-4" />
</div>
<div className="font-medium text-neutral-subtle">Add team</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}
interface NavItem {
title: string;
url?: string;
icon?: IconName;
isActive?: boolean;
items?: {
title: string;
url?: string;
}[];
}
interface NavMainProps {
items: NavItem[];
}
function NavMain({ items }: NavMainProps) {
return (
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
<Collapsible
key={item.title}
defaultOpen={item.isActive}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{item.icon && <Icon name={item.icon} />}
<span className="group-data-[collapsible=icon]:hidden">
{item.title}
</span>
<Icon
name="ChevronRight"
className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90 group-data-[collapsible=icon]:hidden"
/>
</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>
);
}
interface Project {
name: string;
url?: string;
icon: IconName;
}
interface NavProjectsProps {
projects: Project[];
}
function NavProjects({ projects }: NavProjectsProps) {
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}>
<Icon name={item.icon} />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction showOnHover>
<Icon name="EllipsisVertical" color="neutral-mid" />
<span className="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-48 rounded-lg z-[51]"
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}
>
<DropdownMenuItem>
<Icon name="Folder" className="text-neutral-subtle" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="ArrowRight" className="text-neutral-subtle" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="Trash" className="text-neutral-subtle" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
);
}
interface User {
name: string;
email: string;
avatar: string;
}
interface NavUserProps {
user: User;
}
function NavUser({ user }: NavUserProps) {
const { isMobile } = useSidebar();
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-neutral-strong data-[state=open]:text-neutral-subtle"
>
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
<span className="truncate font-semibold">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
<Icon
name="ChevronDown"
className="ml-auto size-4 group-data-[collapsible=icon]:hidden"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-full min-w-56 rounded-lg z-[51]"
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-left text-sm">
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
</div>
</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="Sparkles" />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="BadgeCheck" />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="CreditCard" />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="Bell" />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="LogOut" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}
function AppSidebar({ open, collapsible = "icon" }: AppSidebarProps) {
const { setOpen } = useSidebar();
useEffect(() => {
if (open !== undefined) {
setOpen(open);
}
}, [open]);
const data: AppData = {
user: {
name: "Gustavo",
email: "g.miro@example.com",
avatar:
"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2248%22 height=%2248%22%3E%3Crect width=%2248%22 height=%2248%22 rx=%2224%22 fill=%22%236366f1%22/%3E%3C/svg%3E",
},
teams: [
{
name: "Acme Inc",
logo: "GalleryVerticalEnd",
plan: "Startup",
},
{
name: "Acme Corp.",
logo: "AudioWaveform",
plan: "Startup",
},
{
name: "Evil Corp.",
logo: "Command",
plan: "Free",
},
],
navMain: [
{
title: "Playground",
icon: "SquareCode",
isActive: true,
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
{
title: "Models",
icon: "Bot",
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
{
title: "Documentation",
icon: "BookOpen",
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
{
title: "Settings",
icon: "Settings2",
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
],
projects: [
{
name: "Design Engineering",
icon: "Frame",
},
{
name: "Sales & Marketing",
icon: "ChartPie",
},
{
name: "Development",
icon: "Code",
},
{
name: "Advertisement",
icon: "Radio",
},
],
};
return (
<Sidebar collapsible={collapsible} className="h-full min-h-0">
<SidebarHeader>
<TeamSwitcher teams={data.teams} />
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavProjects projects={data.projects} />
</SidebarContent>
<SidebarFooter>
<NavUser user={data.user} />
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
}
function SidebarExample({ open }: SidebarExampleProps) {
const [expanded, setExpanded] = React.useState(open ?? true);
return (
<div className="h-[560px] w-[900px] max-w-full relative overflow-hidden [transform:translateZ(0)]">
<SidebarProvider
open={expanded}
onOpenChange={setExpanded}
className="min-h-0 h-full"
>
<AppSidebar />
<SidebarInset className="min-h-0 h-full">
<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="-ml-1" />
</div>
</header>
</SidebarInset>
</SidebarProvider>
</div>
);
}
<div className="h-[560px]">
<SidebarExample open />
</div>;
Collapsed
import {
SidebarProvider,
useSidebar,
Sidebar,
SidebarHeader,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarMenuSub,
SidebarMenuSubItem,
SidebarMenuSubButton,
SidebarMenuAction,
SidebarFooter,
SidebarRail,
SidebarInset,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { useEffect, useState } from "react";
import type { IconName } from "@/components/ui/icon";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuShortcut,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Icon } from "@/components/ui/icon";
import {
Collapsible,
CollapsibleTrigger,
CollapsibleContent,
} from "@/components/ui/collapsible";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import * as React from "react";
interface SidebarExampleProps {
open?: boolean;
}
interface AppSidebarProps {
open?: boolean;
collapsible?: "icon" | "offcanvas" | "none";
}
interface UserData {
name: string;
email: string;
avatar: string;
}
interface TeamData {
name: string;
logo: IconName;
plan: string;
}
interface AppNavItem {
title: string;
icon: IconName;
isActive?: boolean;
items?: {
title: string;
url?: string;
}[];
}
interface ProjectData {
name: string;
icon: IconName;
}
interface AppData {
user: UserData;
teams: TeamData[];
navMain: AppNavItem[];
projects: ProjectData[];
}
interface Team {
name: string;
logo: IconName;
plan: string;
}
interface TeamSwitcherProps {
teams: Team[];
}
function TeamSwitcher({ teams }: TeamSwitcherProps) {
const { isMobile } = useSidebar();
const [activeTeam, setActiveTeam] = useState(teams[0]);
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton size="lg">
<div className="rounded-lg p-2 bg-brand-strong">
<Icon name="Compass" className="text-white" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
<span className="truncate font-semibold">
{activeTeam.name}
</span>
<span className="truncate text-xs">{activeTeam.plan}</span>
</div>
<Icon
name="ChevronDown"
className="ml-auto group-data-[collapsible=icon]:hidden"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--reka-dropdownmenu-trigger-width] min-w-56 rounded-lg z-[51]"
align="start"
side={isMobile ? "bottom" : "right"}
sideOffset={4}
>
<DropdownMenuGroup>
<DropdownMenuLabel className="text-xs text-neutral-subtle">
Teams
</DropdownMenuLabel>
</DropdownMenuGroup>
{teams.map((team, index) => (
<DropdownMenuItem
key={team.name}
className="gap-2 p-2"
onClick={() => setActiveTeam(team)}
>
<div className="flex size-6 items-center justify-center rounded-sm border">
<Icon name={team.logo} className="size-4 shrink-0" />
</div>
{team.name}
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-2 p-2">
<div className="flex size-6 items-center justify-center rounded-md border bg-layer-below">
<Icon name="Plus" className="size-4" />
</div>
<div className="font-medium text-neutral-subtle">Add team</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}
interface NavItem {
title: string;
url?: string;
icon?: IconName;
isActive?: boolean;
items?: {
title: string;
url?: string;
}[];
}
interface NavMainProps {
items: NavItem[];
}
function NavMain({ items }: NavMainProps) {
return (
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
<Collapsible
key={item.title}
defaultOpen={item.isActive}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{item.icon && <Icon name={item.icon} />}
<span className="group-data-[collapsible=icon]:hidden">
{item.title}
</span>
<Icon
name="ChevronRight"
className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90 group-data-[collapsible=icon]:hidden"
/>
</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>
);
}
interface Project {
name: string;
url?: string;
icon: IconName;
}
interface NavProjectsProps {
projects: Project[];
}
function NavProjects({ projects }: NavProjectsProps) {
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}>
<Icon name={item.icon} />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction showOnHover>
<Icon name="EllipsisVertical" color="neutral-mid" />
<span className="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-48 rounded-lg z-[51]"
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}
>
<DropdownMenuItem>
<Icon name="Folder" className="text-neutral-subtle" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="ArrowRight" className="text-neutral-subtle" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="Trash" className="text-neutral-subtle" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
);
}
interface User {
name: string;
email: string;
avatar: string;
}
interface NavUserProps {
user: User;
}
function NavUser({ user }: NavUserProps) {
const { isMobile } = useSidebar();
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-neutral-strong data-[state=open]:text-neutral-subtle"
>
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
<span className="truncate font-semibold">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
<Icon
name="ChevronDown"
className="ml-auto size-4 group-data-[collapsible=icon]:hidden"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-full min-w-56 rounded-lg z-[51]"
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-left text-sm">
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
</div>
</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="Sparkles" />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="BadgeCheck" />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="CreditCard" />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="Bell" />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="LogOut" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}
function AppSidebar({ open, collapsible = "icon" }: AppSidebarProps) {
const { setOpen } = useSidebar();
useEffect(() => {
if (open !== undefined) {
setOpen(open);
}
}, [open]);
const data: AppData = {
user: {
name: "Gustavo",
email: "g.miro@example.com",
avatar:
"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2248%22 height=%2248%22%3E%3Crect width=%2248%22 height=%2248%22 rx=%2224%22 fill=%22%236366f1%22/%3E%3C/svg%3E",
},
teams: [
{
name: "Acme Inc",
logo: "GalleryVerticalEnd",
plan: "Startup",
},
{
name: "Acme Corp.",
logo: "AudioWaveform",
plan: "Startup",
},
{
name: "Evil Corp.",
logo: "Command",
plan: "Free",
},
],
navMain: [
{
title: "Playground",
icon: "SquareCode",
isActive: true,
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
{
title: "Models",
icon: "Bot",
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
{
title: "Documentation",
icon: "BookOpen",
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
{
title: "Settings",
icon: "Settings2",
items: [
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
{ title: "Menu Item" },
],
},
],
projects: [
{
name: "Design Engineering",
icon: "Frame",
},
{
name: "Sales & Marketing",
icon: "ChartPie",
},
{
name: "Development",
icon: "Code",
},
{
name: "Advertisement",
icon: "Radio",
},
],
};
return (
<Sidebar collapsible={collapsible} className="h-full min-h-0">
<SidebarHeader>
<TeamSwitcher teams={data.teams} />
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavProjects projects={data.projects} />
</SidebarContent>
<SidebarFooter>
<NavUser user={data.user} />
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
}
function SidebarExample({ open }: SidebarExampleProps) {
const [expanded, setExpanded] = React.useState(open ?? true);
return (
<div className="h-[560px] w-[900px] max-w-full relative overflow-hidden [transform:translateZ(0)]">
<SidebarProvider
open={expanded}
onOpenChange={setExpanded}
className="min-h-0 h-full"
>
<AppSidebar />
<SidebarInset className="min-h-0 h-full">
<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="-ml-1" />
</div>
</header>
</SidebarInset>
</SidebarProvider>
</div>
);
}
<div className="h-[560px]">
<SidebarExample open={false} />
</div>;
API reference
Frontfriend-owned props. Native HTML and inherited primitive props
remain documented by Base UI.
Sidebar
| Prop | Type | Default | Description |
collapsible | "offcanvas" | "icon" | "none" | "offcanvas" | |
mobileCloseLabel | string | "Close" | |
mobileDescription | string | "Displays the mobile sidebar." | |
mobileTitle | string | "Sidebar" | |
side | "left" | "right" | "left" | |
variant | "sidebar" | "floating" | "inset" | "sidebar" | |
SidebarGroupAction
| Prop | Type | Default | Description |
asChild | boolean | false | |
SidebarGroupLabel
| Prop | Type | Default | Description |
asChild | boolean | false | |
SidebarMenuAction
| Prop | Type | Default | Description |
asChild | boolean | false | |
showOnHover | boolean | false | |
SidebarMenuButton
| Prop | Type | Default | Description |
asChild | boolean | false | |
isActive | boolean | false | |
size | "default" | "sm" | "lg" | "default" | |
tooltip | string | React.ComponentProps<typeof TooltipContent> | — | |
variant | "default" | "outline" | "default" | |
SidebarMenuSkeleton
| Prop | Type | Default | Description |
showIcon | boolean | false | |
SidebarMenuSubButton
| Prop | Type | Default | Description |
asChild | boolean | false | |
isActive | boolean | false | |
size | "sm" | "md" | "md" | |
SidebarProvider
| Prop | Type | Default | Description |
defaultOpen | boolean | true | |
onOpenChange | (open: boolean) => void | — | |
open | boolean | — | |
Configuration determines the actual classes and available styling hooks.
See Theming & component configuration before introducing
a repeated local override.