Meet Frontfriend.Your brand. Your rules. Your agents, connected.Explore what’s new
DocumentationSidebar

Sidebar

A composable, themeable and customizable sidebar component.

Interactive preview

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 React Aria.

Installation

Complete project setup first. Your configuration must select react/aria and include your Design System ID.

pnpm exec frontfriend download sidebar -f react --primitive aria

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 {
  DropdownMenuTrigger,
  DropdownMenu,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuItem,
  DropdownMenuShortcut,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Icon } from "@/components/ui/icon";
import { Collapsible, 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 {
  url: string;
  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]);
  if (!activeTeam) return null;
  return (
    <SidebarMenu>
      <SidebarMenuItem>
        <DropdownMenuTrigger>
          <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>
          <DropdownMenu
            className="w-[var(--trigger-width)] min-w-56 rounded-lg z-[51]"
            placement={isMobile ? "bottom start" : "right top"}
            offset={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"
                onAction={() => 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>
          </DropdownMenu>
        </DropdownMenuTrigger>
      </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}
            defaultExpanded={item.isActive}
            className="group/collapsible"
            render={(props) => <SidebarMenuItem {...props} />}
          >
            <SidebarMenuButton slot="trigger" 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-expanded/collapsible:rotate-90 group-data-[collapsible=icon]:hidden"
              />
            </SidebarMenuButton>
            <CollapsibleContent>
              <SidebarMenuSub>
                {item.items?.map((subItem) => (
                  <SidebarMenuSubItem key={subItem.title}>
                    <SidebarMenuSubButton href={subItem.url ?? "#"}>
                      <span>{subItem.title}</span>
                    </SidebarMenuSubButton>
                  </SidebarMenuSubItem>
                ))}
              </SidebarMenuSub>
            </CollapsibleContent>
          </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 render={<a href={item.url ?? "#"} />}>
              <Icon name={item.icon} />
              <span>{item.name}</span>
            </SidebarMenuButton>
            <DropdownMenuTrigger>
              <SidebarMenuAction showOnHover>
                <Icon name="EllipsisVertical" color="neutral-mid" />
                <span className="sr-only">More {item.name}</span>
              </SidebarMenuAction>
              <DropdownMenu
                className="w-48 rounded-lg z-[51]"
                placement={isMobile ? "bottom end" : "right top"}
              >
                <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>
              </DropdownMenu>
            </DropdownMenuTrigger>
          </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>
        <DropdownMenuTrigger>
          <SidebarMenuButton
            size="lg"
            className="aria-expanded:bg-neutral-strong aria-expanded: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>
          <DropdownMenu
            className="w-full min-w-56 rounded-lg z-[51]"
            placement={isMobile ? "bottom end" : "right bottom"}
            offset={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>
          </DropdownMenu>
        </DropdownMenuTrigger>
      </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: "Overview", url: "#section-1" },
          { title: "Recent activity", url: "#section-2" },
          { title: "Favorites", url: "#section-3" },
          { title: "Archive", url: "#section-4" },
        ],
      },
      {
        title: "Models",
        icon: "Bot",
        items: [
          { title: "Overview", url: "#section-5" },
          { title: "Recent activity", url: "#section-6" },
          { title: "Favorites", url: "#section-7" },
          { title: "Archive", url: "#section-8" },
        ],
      },
      {
        title: "Documentation",
        icon: "BookOpen",
        items: [
          { title: "Overview", url: "#section-9" },
          { title: "Recent activity", url: "#section-10" },
          { title: "Favorites", url: "#section-11" },
          { title: "Archive", url: "#section-12" },
        ],
      },
      {
        title: "Settings",
        icon: "Settings2",
        items: [
          { title: "Overview", url: "#section-13" },
          { title: "Recent activity", url: "#section-14" },
          { title: "Favorites", url: "#section-15" },
          { title: "Archive", url: "#section-16" },
        ],
      },
    ],
    projects: [
      {
        name: "Design Engineering",
        url: "#design-engineering",
        icon: "Frame",
      },
      {
        name: "Sales & Marketing",
        url: "#sales-marketing",
        icon: "ChartPie",
      },
      {
        name: "Development",
        url: "#development",
        icon: "Code",
      },
      {
        name: "Advertisement",
        url: "#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 {
  DropdownMenuTrigger,
  DropdownMenu,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuItem,
  DropdownMenuShortcut,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Icon } from "@/components/ui/icon";
import { Collapsible, 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 {
  url: string;
  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]);
  if (!activeTeam) return null;
  return (
    <SidebarMenu>
      <SidebarMenuItem>
        <DropdownMenuTrigger>
          <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>
          <DropdownMenu
            className="w-[var(--trigger-width)] min-w-56 rounded-lg z-[51]"
            placement={isMobile ? "bottom start" : "right top"}
            offset={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"
                onAction={() => 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>
          </DropdownMenu>
        </DropdownMenuTrigger>
      </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}
            defaultExpanded={item.isActive}
            className="group/collapsible"
            render={(props) => <SidebarMenuItem {...props} />}
          >
            <SidebarMenuButton slot="trigger" 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-expanded/collapsible:rotate-90 group-data-[collapsible=icon]:hidden"
              />
            </SidebarMenuButton>
            <CollapsibleContent>
              <SidebarMenuSub>
                {item.items?.map((subItem) => (
                  <SidebarMenuSubItem key={subItem.title}>
                    <SidebarMenuSubButton href={subItem.url ?? "#"}>
                      <span>{subItem.title}</span>
                    </SidebarMenuSubButton>
                  </SidebarMenuSubItem>
                ))}
              </SidebarMenuSub>
            </CollapsibleContent>
          </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 render={<a href={item.url ?? "#"} />}>
              <Icon name={item.icon} />
              <span>{item.name}</span>
            </SidebarMenuButton>
            <DropdownMenuTrigger>
              <SidebarMenuAction showOnHover>
                <Icon name="EllipsisVertical" color="neutral-mid" />
                <span className="sr-only">More {item.name}</span>
              </SidebarMenuAction>
              <DropdownMenu
                className="w-48 rounded-lg z-[51]"
                placement={isMobile ? "bottom end" : "right top"}
              >
                <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>
              </DropdownMenu>
            </DropdownMenuTrigger>
          </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>
        <DropdownMenuTrigger>
          <SidebarMenuButton
            size="lg"
            className="aria-expanded:bg-neutral-strong aria-expanded: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>
          <DropdownMenu
            className="w-full min-w-56 rounded-lg z-[51]"
            placement={isMobile ? "bottom end" : "right bottom"}
            offset={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>
          </DropdownMenu>
        </DropdownMenuTrigger>
      </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: "Overview", url: "#section-1" },
          { title: "Recent activity", url: "#section-2" },
          { title: "Favorites", url: "#section-3" },
          { title: "Archive", url: "#section-4" },
        ],
      },
      {
        title: "Models",
        icon: "Bot",
        items: [
          { title: "Overview", url: "#section-5" },
          { title: "Recent activity", url: "#section-6" },
          { title: "Favorites", url: "#section-7" },
          { title: "Archive", url: "#section-8" },
        ],
      },
      {
        title: "Documentation",
        icon: "BookOpen",
        items: [
          { title: "Overview", url: "#section-9" },
          { title: "Recent activity", url: "#section-10" },
          { title: "Favorites", url: "#section-11" },
          { title: "Archive", url: "#section-12" },
        ],
      },
      {
        title: "Settings",
        icon: "Settings2",
        items: [
          { title: "Overview", url: "#section-13" },
          { title: "Recent activity", url: "#section-14" },
          { title: "Favorites", url: "#section-15" },
          { title: "Archive", url: "#section-16" },
        ],
      },
    ],
    projects: [
      {
        name: "Design Engineering",
        url: "#design-engineering",
        icon: "Frame",
      },
      {
        name: "Sales & Marketing",
        url: "#sales-marketing",
        icon: "ChartPie",
      },
      {
        name: "Development",
        url: "#development",
        icon: "Code",
      },
      {
        name: "Advertisement",
        url: "#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 />;

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 {
  DropdownMenuTrigger,
  DropdownMenu,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuItem,
  DropdownMenuShortcut,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Icon } from "@/components/ui/icon";
import { Collapsible, 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 {
  url: string;
  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]);
  if (!activeTeam) return null;
  return (
    <SidebarMenu>
      <SidebarMenuItem>
        <DropdownMenuTrigger>
          <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>
          <DropdownMenu
            className="w-[var(--trigger-width)] min-w-56 rounded-lg z-[51]"
            placement={isMobile ? "bottom start" : "right top"}
            offset={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"
                onAction={() => 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>
          </DropdownMenu>
        </DropdownMenuTrigger>
      </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}
            defaultExpanded={item.isActive}
            className="group/collapsible"
            render={(props) => <SidebarMenuItem {...props} />}
          >
            <SidebarMenuButton slot="trigger" 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-expanded/collapsible:rotate-90 group-data-[collapsible=icon]:hidden"
              />
            </SidebarMenuButton>
            <CollapsibleContent>
              <SidebarMenuSub>
                {item.items?.map((subItem) => (
                  <SidebarMenuSubItem key={subItem.title}>
                    <SidebarMenuSubButton href={subItem.url ?? "#"}>
                      <span>{subItem.title}</span>
                    </SidebarMenuSubButton>
                  </SidebarMenuSubItem>
                ))}
              </SidebarMenuSub>
            </CollapsibleContent>
          </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 render={<a href={item.url ?? "#"} />}>
              <Icon name={item.icon} />
              <span>{item.name}</span>
            </SidebarMenuButton>
            <DropdownMenuTrigger>
              <SidebarMenuAction showOnHover>
                <Icon name="EllipsisVertical" color="neutral-mid" />
                <span className="sr-only">More {item.name}</span>
              </SidebarMenuAction>
              <DropdownMenu
                className="w-48 rounded-lg z-[51]"
                placement={isMobile ? "bottom end" : "right top"}
              >
                <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>
              </DropdownMenu>
            </DropdownMenuTrigger>
          </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>
        <DropdownMenuTrigger>
          <SidebarMenuButton
            size="lg"
            className="aria-expanded:bg-neutral-strong aria-expanded: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>
          <DropdownMenu
            className="w-full min-w-56 rounded-lg z-[51]"
            placement={isMobile ? "bottom end" : "right bottom"}
            offset={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>
          </DropdownMenu>
        </DropdownMenuTrigger>
      </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: "Overview", url: "#section-1" },
          { title: "Recent activity", url: "#section-2" },
          { title: "Favorites", url: "#section-3" },
          { title: "Archive", url: "#section-4" },
        ],
      },
      {
        title: "Models",
        icon: "Bot",
        items: [
          { title: "Overview", url: "#section-5" },
          { title: "Recent activity", url: "#section-6" },
          { title: "Favorites", url: "#section-7" },
          { title: "Archive", url: "#section-8" },
        ],
      },
      {
        title: "Documentation",
        icon: "BookOpen",
        items: [
          { title: "Overview", url: "#section-9" },
          { title: "Recent activity", url: "#section-10" },
          { title: "Favorites", url: "#section-11" },
          { title: "Archive", url: "#section-12" },
        ],
      },
      {
        title: "Settings",
        icon: "Settings2",
        items: [
          { title: "Overview", url: "#section-13" },
          { title: "Recent activity", url: "#section-14" },
          { title: "Favorites", url: "#section-15" },
          { title: "Archive", url: "#section-16" },
        ],
      },
    ],
    projects: [
      {
        name: "Design Engineering",
        url: "#design-engineering",
        icon: "Frame",
      },
      {
        name: "Sales & Marketing",
        url: "#sales-marketing",
        icon: "ChartPie",
      },
      {
        name: "Development",
        url: "#development",
        icon: "Code",
      },
      {
        name: "Advertisement",
        url: "#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={false} />;

API reference

Frontfriend-owned props. Native HTML and inherited primitive props remain documented by React Aria.

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.