HextaUI

Sidebar

Uma barra lateral de app que recolhe para ícones ou fora da tela, permanece fixa sob o seu cabeçalho e vira uma sheet com deslize no celular.

import {
  IconBook,
  IconChartBar,
  IconChevronRight,
  IconDots,
  IconFolder,
  IconHome,
  IconInbox,
  IconLayoutDashboard,
  IconLogout,
  IconSelector,
  IconSettings,
  IconShare,
  IconTrash,
} from "@tabler/icons-react"

import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuAction,
  SidebarMenuBadge,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
} from "@/components/ui/sidebar"

const projects = [
  { name: "Design engineering", icon: IconFolder },
  { name: "Sales & marketing", icon: IconChartBar },
  { name: "Travel", icon: IconBook },
]

export function SidebarDemo() {
  return (
    <SidebarProvider className="h-128 min-h-0">
      <Sidebar collapsible="icon">
        <SidebarHeader>
          <SidebarMenu>
            <SidebarMenuItem>
              <DropdownMenu>
                <DropdownMenuTrigger render={<SidebarMenuButton size="lg" />}>
                  <div className="flex size-8 shrink-0 items-center justify-center rounded-md bg-sidebar-primary text-sm font-semibold text-sidebar-primary-foreground">
                    A
                  </div>
                  <div className="grid min-w-0 flex-1 leading-tight">
                    <span className="truncate font-medium">Acme Inc</span>
                    <span className="truncate text-xs text-muted-foreground">
                      Enterprise
                    </span>
                  </div>
                  <IconSelector className="ms-auto" />
                </DropdownMenuTrigger>
                <DropdownMenuContent align="start" className="w-56">
                  <DropdownMenuItem>Acme Inc</DropdownMenuItem>
                  <DropdownMenuItem>Acme Corp.</DropdownMenuItem>
                  <DropdownMenuItem>Evil Corp.</DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarHeader>
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupLabel>Platform</SidebarGroupLabel>
            <SidebarGroupContent>
              <SidebarMenu>
                <SidebarMenuItem>
                  <SidebarMenuButton
                    render={<a href="#" />}
                    isActive
                    tooltip="Home"
                  >
                    <IconHome />
                    <span>Home</span>
                  </SidebarMenuButton>
                </SidebarMenuItem>
                <SidebarMenuItem>
                  <SidebarMenuButton render={<a href="#" />} tooltip="Inbox">
                    <IconInbox />
                    <span>Inbox</span>
                  </SidebarMenuButton>
                  <SidebarMenuBadge>24</SidebarMenuBadge>
                </SidebarMenuItem>
                <Collapsible
                  defaultOpen
                  render={<SidebarMenuItem />}
                  className="group/collapsible"
                >
                  <CollapsibleTrigger
                    render={<SidebarMenuButton tooltip="Dashboards" />}
                  >
                    <IconLayoutDashboard />
                    <span>Dashboards</span>
                    <IconChevronRight className="ms-auto duration-200 group-data-open/collapsible:rotate-90 motion-safe:transition-transform rtl:-scale-x-100" />
                  </CollapsibleTrigger>
                  <CollapsibleContent>
                    <SidebarMenuSub>
                      <SidebarMenuSubItem>
                        <SidebarMenuSubButton href="#">
                          <span>Overview</span>
                        </SidebarMenuSubButton>
                      </SidebarMenuSubItem>
                      <SidebarMenuSubItem>
                        <SidebarMenuSubButton href="#">
                          <span>Revenue</span>
                        </SidebarMenuSubButton>
                      </SidebarMenuSubItem>
                    </SidebarMenuSub>
                  </CollapsibleContent>
                </Collapsible>
                <SidebarMenuItem>
                  <SidebarMenuButton render={<a href="#" />} tooltip="Settings">
                    <IconSettings />
                    <span>Settings</span>
                  </SidebarMenuButton>
                </SidebarMenuItem>
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
          <SidebarGroup>
            <SidebarGroupLabel>Projects</SidebarGroupLabel>
            <SidebarGroupContent>
              <SidebarMenu>
                {projects.map((project) => (
                  <SidebarMenuItem key={project.name}>
                    <SidebarMenuButton
                      render={<a href="#" />}
                      tooltip={project.name}
                    >
                      <project.icon />
                      <span>{project.name}</span>
                    </SidebarMenuButton>
                    <DropdownMenu>
                      <DropdownMenuTrigger
                        render={<SidebarMenuAction showOnHover />}
                      >
                        <IconDots />
                        <span className="sr-only">More</span>
                      </DropdownMenuTrigger>
                      <DropdownMenuContent side="right" align="start">
                        <DropdownMenuItem>
                          <IconShare />
                          Share
                        </DropdownMenuItem>
                        <DropdownMenuSeparator />
                        <DropdownMenuItem variant="destructive">
                          <IconTrash />
                          Delete
                        </DropdownMenuItem>
                      </DropdownMenuContent>
                    </DropdownMenu>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
        <SidebarFooter>
          <SidebarMenu>
            <SidebarMenuItem>
              <DropdownMenu>
                <DropdownMenuTrigger render={<SidebarMenuButton size="lg" />}>
                  <div className="flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-medium">
                    OM
                  </div>
                  <div className="grid min-w-0 flex-1 leading-tight">
                    <span className="truncate font-medium">Olivia Martin</span>
                    <span className="truncate text-xs text-muted-foreground">
                      [email protected]
                    </span>
                  </div>
                  <IconSelector className="ms-auto" />
                </DropdownMenuTrigger>
                <DropdownMenuContent side="top" align="start" className="w-56">
                  <DropdownMenuItem>
                    <IconSettings />
                    Account
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <IconLogout />
                    Log out
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarFooter>
        <SidebarRail />
      </Sidebar>
      <SidebarInset>
        <header className="flex h-14 shrink-0 items-center gap-2 border-b px-4">
          <SidebarTrigger className="-ms-1" />
          <Breadcrumb>
            <BreadcrumbList>
              <BreadcrumbItem>
                <BreadcrumbLink href="#">Platform</BreadcrumbLink>
              </BreadcrumbItem>
              <BreadcrumbSeparator />
              <BreadcrumbItem>
                <BreadcrumbPage>Home</BreadcrumbPage>
              </BreadcrumbItem>
            </BreadcrumbList>
          </Breadcrumb>
        </header>
        <div className="flex flex-1 flex-col gap-4 p-4">
          <div className="grid gap-4 sm:grid-cols-3">
            <div className="aspect-video rounded-xl bg-muted" />
            <div className="aspect-video rounded-xl bg-muted" />
            <div className="aspect-video rounded-xl bg-muted" />
          </div>
          <div className="min-h-32 flex-1 rounded-xl bg-muted" />
        </div>
      </SidebarInset>
    </SidebarProvider>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/sidebar.json

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarTrigger,
} from "@/components/ui/sidebar"

Envolva seu layout em <SidebarProvider /> e coloque a página em <SidebarInset />, depois da sidebar.

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <SidebarProvider>
      <AppSidebar />
      <SidebarInset>
        <SidebarTrigger />
        {children}
      </SidebarInset>
    </SidebarProvider>
  )
}
export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarHeader />
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Application</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              <SidebarMenuItem>
                <SidebarMenuButton render={<a href="/" />} isActive>
                  <IconHome />
                  <span>Home</span>
                </SidebarMenuButton>
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
      <SidebarFooter />
    </Sidebar>
  )
}
SidebarProvider
├── Sidebar
│   ├── SidebarHeader
│   ├── SidebarContent
│   │   └── SidebarGroup
│   │       ├── SidebarGroupLabel
│   │       ├── SidebarGroupAction
│   │       └── SidebarGroupContent
│   │           └── SidebarMenu
│   │               └── SidebarMenuItem
│   │                   ├── SidebarMenuButton
│   │                   ├── SidebarMenuAction
│   │                   ├── SidebarMenuBadge
│   │                   └── SidebarMenuSub
│   │                       └── SidebarMenuSubItem
│   │                           └── SidebarMenuSubButton
│   ├── SidebarFooter
│   └── SidebarRail
└── SidebarInset
    └── SidebarTrigger
  • SidebarProvider guarda o estado de aberto, o atalho de teclado e as larguras.
  • Sidebar é uma coluna que permanece fixa enquanto a página rola. Abaixo de 768px vira um sheet.
  • SidebarHeader e SidebarFooter ficam no lugar. SidebarContent rola entre eles.
  • SidebarGroup é uma seção com rótulo e ação opcionais. SidebarMenu contém os links.
  • SidebarInset é a página ao lado da sidebar.

Variantes

variant define a aparência: uma sidebar de altura total, um painel floating, ou inset, em que a página vira um card sobre a cor da sidebar. Alterne entre elas para ver o layout animar.

"use client"

import * as React from "react"
import {
  IconHome,
  IconInbox,
  IconSearch,
  IconSettings,
} from "@tabler/icons-react"

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarTrigger,
} from "@/components/ui/sidebar"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

type Variant = "sidebar" | "floating" | "inset"

const items = [
  { title: "Home", icon: IconHome },
  { title: "Inbox", icon: IconInbox },
  { title: "Search", icon: IconSearch },
  { title: "Settings", icon: IconSettings },
]

export function SidebarVariants() {
  const [variant, setVariant] = React.useState<Variant>("inset")
  const [active, setActive] = React.useState("Home")

  return (
    <SidebarProvider className="h-112 min-h-0">
      <Sidebar variant={variant}>
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupLabel>Application</SidebarGroupLabel>
            <SidebarGroupContent>
              <SidebarMenu>
                {items.map((item) => (
                  <SidebarMenuItem key={item.title}>
                    <SidebarMenuButton
                      isActive={active === item.title}
                      onClick={() => setActive(item.title)}
                    >
                      <item.icon />
                      <span>{item.title}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
      </Sidebar>
      <SidebarInset>
        <header className="flex h-14 items-center gap-2 px-4">
          <SidebarTrigger className="-ms-1" />
          <ToggleGroup
            aria-label="Variant"
            value={[variant]}
            onValueChange={(value) => {
              if (value[0]) {
                setVariant(value[0] as Variant)
              }
            }}
          >
            <ToggleGroupItem value="sidebar">Sidebar</ToggleGroupItem>
            <ToggleGroupItem value="floating">Floating</ToggleGroupItem>
            <ToggleGroupItem value="inset">Inset</ToggleGroupItem>
          </ToggleGroup>
        </header>
      </SidebarInset>
    </SidebarProvider>
  )
}

Collapsible

offcanvas desliza a sidebar para fora da vista, icon a reduz aos seus ícones e mostra cada rótulo em um tooltip, e none a mantém aberta. Pressione ⌘B ou Ctrl+B para alternar a sidebar em que você está trabalhando.

"use client"

import * as React from "react"
import {
  IconHome,
  IconInbox,
  IconSearch,
  IconSettings,
} from "@tabler/icons-react"

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarTrigger,
} from "@/components/ui/sidebar"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

type CollapsibleMode = "offcanvas" | "icon" | "none"

const items = [
  { title: "Home", icon: IconHome },
  { title: "Inbox", icon: IconInbox },
  { title: "Search", icon: IconSearch },
  { title: "Settings", icon: IconSettings },
]

export function SidebarCollapsible() {
  const [collapsible, setCollapsible] = React.useState<CollapsibleMode>("icon")
  const [active, setActive] = React.useState("Home")

  return (
    <SidebarProvider className="h-112 min-h-0">
      <Sidebar collapsible={collapsible}>
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupLabel>Application</SidebarGroupLabel>
            <SidebarGroupContent>
              <SidebarMenu>
                {items.map((item) => (
                  <SidebarMenuItem key={item.title}>
                    <SidebarMenuButton
                      isActive={active === item.title}
                      onClick={() => setActive(item.title)}
                      tooltip={item.title}
                    >
                      <item.icon />
                      <span>{item.title}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
      </Sidebar>
      <SidebarInset>
        <header className="flex h-14 items-center gap-2 px-4">
          <SidebarTrigger className="-ms-1" />
          <ToggleGroup
            aria-label="Collapsible"
            value={[collapsible]}
            onValueChange={(value) => {
              if (value[0]) {
                setCollapsible(value[0] as CollapsibleMode)
              }
            }}
          >
            <ToggleGroupItem value="offcanvas">Offcanvas</ToggleGroupItem>
            <ToggleGroupItem value="icon">Icon</ToggleGroupItem>
            <ToggleGroupItem value="none">None</ToggleGroupItem>
          </ToggleGroup>
        </header>
      </SidebarInset>
    </SidebarProvider>
  )
}

Grupos e submenus recolhíveis

Envolva um SidebarGroup ou um SidebarMenuItem em um Collapsible, renderize o rótulo ou o botão como gatilho e aninhe um SidebarMenuSub.

import {
  IconChevronDown,
  IconChevronRight,
  IconFile,
  IconFolder,
} from "@tabler/icons-react"

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"
import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarProvider,
  SidebarTrigger,
} from "@/components/ui/sidebar"

const sections = [
  {
    title: "Getting started",
    items: ["Installation", "Project structure", "Deploying"],
  },
  {
    title: "Building your app",
    items: ["Routing", "Data fetching", "Rendering", "Caching", "Styling"],
  },
  {
    title: "API reference",
    items: ["Components", "File conventions", "Functions"],
  },
]

export function SidebarSubmenus() {
  return (
    <SidebarProvider className="h-128 min-h-0">
      <Sidebar>
        <SidebarContent>
          <Collapsible defaultOpen className="group/collapsible">
            <SidebarGroup>
              <SidebarGroupLabel render={<CollapsibleTrigger />}>
                Documentation
                <IconChevronDown className="ms-auto duration-200 group-data-open/collapsible:rotate-180 motion-safe:transition-transform" />
              </SidebarGroupLabel>
              <CollapsibleContent>
                <SidebarGroupContent>
                  <SidebarMenu>
                    {sections.map((section, index) => (
                      <Collapsible
                        key={section.title}
                        defaultOpen={index === 1}
                        render={<SidebarMenuItem />}
                        className="group/section"
                      >
                        <CollapsibleTrigger render={<SidebarMenuButton />}>
                          <IconFolder />
                          <span>{section.title}</span>
                          <IconChevronRight className="ms-auto duration-200 group-data-open/section:rotate-90 motion-safe:transition-transform rtl:-scale-x-100" />
                        </CollapsibleTrigger>
                        <CollapsibleContent>
                          <SidebarMenuSub>
                            {section.items.map((item) => (
                              <SidebarMenuSubItem key={item}>
                                <SidebarMenuSubButton
                                  href="#"
                                  isActive={item === "Data fetching"}
                                >
                                  <IconFile />
                                  <span>{item}</span>
                                </SidebarMenuSubButton>
                              </SidebarMenuSubItem>
                            ))}
                          </SidebarMenuSub>
                        </CollapsibleContent>
                      </Collapsible>
                    ))}
                  </SidebarMenu>
                </SidebarGroupContent>
              </CollapsibleContent>
            </SidebarGroup>
          </Collapsible>
        </SidebarContent>
      </Sidebar>
      <SidebarInset>
        <header className="flex h-14 items-center px-4">
          <SidebarTrigger className="-ms-1" />
        </header>
      </SidebarInset>
    </SidebarProvider>
  )
}

Lado direito

Defina side="right" e coloque a sidebar depois de SidebarInset. O rail e o sheet mobile seguem o lado.

import { IconAt, IconBell, IconMessage, IconUsers } from "@tabler/icons-react"

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarInset,
  SidebarMenu,
  SidebarMenuBadge,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
} from "@/components/ui/sidebar"

const items = [
  { title: "Mentions", icon: IconAt, count: 3 },
  { title: "Comments", icon: IconMessage, count: 12 },
  { title: "Reminders", icon: IconBell, count: 0 },
  { title: "Followers", icon: IconUsers, count: 128 },
]

export function SidebarRight() {
  return (
    <SidebarProvider className="h-112 min-h-0">
      <SidebarInset>
        <header className="flex h-14 items-center justify-end px-4">
          <SidebarTrigger className="-me-1" />
        </header>
      </SidebarInset>
      <Sidebar side="right">
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupLabel>Activity</SidebarGroupLabel>
            <SidebarGroupContent>
              <SidebarMenu>
                {items.map((item) => (
                  <SidebarMenuItem key={item.title}>
                    <SidebarMenuButton>
                      <item.icon />
                      <span>{item.title}</span>
                    </SidebarMenuButton>
                    {item.count > 0 && (
                      <SidebarMenuBadge>{item.count}</SidebarMenuBadge>
                    )}
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
        <SidebarRail />
      </Sidebar>
    </SidebarProvider>
  )
}

Sob um cabeçalho

A sidebar é sticky, então começa abaixo de tudo o que está acima dela. Com um cabeçalho sticky, defina --sidebar-top com a altura dele e a sidebar fixa abaixo dele e ocupa o resto da tela.

import { IconHome, IconInbox, IconSettings } from "@tabler/icons-react"

import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarTrigger,
} from "@/components/ui/sidebar"

const items = [
  { title: "Home", icon: IconHome },
  { title: "Inbox", icon: IconInbox },
  { title: "Settings", icon: IconSettings },
]

export function SidebarHeaderLayout() {
  return (
    <SidebarProvider className="h-112 min-h-0 flex-col [--sidebar-top:3.5rem]">
      <header className="sticky top-0 z-20 flex h-14 shrink-0 items-center gap-2 border-b bg-background px-4">
        <SidebarTrigger className="-ms-1" />
        <span className="text-sm font-medium">Acme</span>
      </header>
      <div className="flex min-h-0 flex-1">
        <Sidebar>
          <SidebarContent>
            <SidebarGroup>
              <SidebarGroupContent>
                <SidebarMenu>
                  {items.map((item) => (
                    <SidebarMenuItem key={item.title}>
                      <SidebarMenuButton>
                        <item.icon />
                        <span>{item.title}</span>
                      </SidebarMenuButton>
                    </SidebarMenuItem>
                  ))}
                </SidebarMenu>
              </SidebarGroupContent>
            </SidebarGroup>
          </SidebarContent>
          <SidebarFooter>
            <p className="px-2 text-xs text-muted-foreground">v2.4.0</p>
          </SidebarFooter>
        </Sidebar>
        <SidebarInset>
          <div className="flex flex-1 flex-col gap-4 p-4">
            <div className="min-h-32 flex-1 rounded-xl bg-muted" />
          </div>
        </SidebarInset>
      </div>
    </SidebarProvider>
  )
}

Controlado

Passe open e onOpenChange. O gatilho, o rail e o atalho passam todos por onOpenChange.

"use client"

import * as React from "react"
import { IconHome, IconInbox, IconSettings } from "@tabler/icons-react"

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
} from "@/components/ui/sidebar"
import { Switch } from "@/components/ui/switch"

const items = [
  { title: "Home", icon: IconHome },
  { title: "Inbox", icon: IconInbox },
  { title: "Settings", icon: IconSettings },
]

export function SidebarControlled() {
  const [open, setOpen] = React.useState(true)

  return (
    <SidebarProvider
      open={open}
      onOpenChange={setOpen}
      className="h-96 min-h-0"
    >
      <Sidebar collapsible="icon">
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupContent>
              <SidebarMenu>
                {items.map((item) => (
                  <SidebarMenuItem key={item.title}>
                    <SidebarMenuButton tooltip={item.title}>
                      <item.icon />
                      <span>{item.title}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
      </Sidebar>
      <SidebarInset>
        <div className="flex items-center gap-3 p-4">
          <Switch
            id="sidebar-controlled-open"
            checked={open}
            onCheckedChange={setOpen}
          />
          <label htmlFor="sidebar-controlled-open" className="text-sm">
            Sidebar {open ? "expanded" : "collapsed"}
          </label>
        </div>
      </SidebarInset>
    </SidebarProvider>
  )
}

Carregando

SidebarMenuSkeleton preenche um menu enquanto ele carrega. As larguras variam por linha e coincidem entre servidor e cliente.

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarInset,
  SidebarMenu,
  SidebarMenuItem,
  SidebarMenuSkeleton,
  SidebarProvider,
  SidebarTrigger,
} from "@/components/ui/sidebar"

export function SidebarSkeleton() {
  return (
    <SidebarProvider className="h-96 min-h-0">
      <Sidebar>
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupLabel>Projects</SidebarGroupLabel>
            <SidebarGroupContent>
              <SidebarMenu>
                {Array.from({ length: 6 }).map((_, index) => (
                  <SidebarMenuItem key={index}>
                    <SidebarMenuSkeleton showIcon />
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
      </Sidebar>
      <SidebarInset>
        <header className="flex h-14 items-center px-4">
          <SidebarTrigger className="-ms-1" />
        </header>
      </SidebarInset>
    </SidebarProvider>
  )
}

Da direita para a esquerda

Defina dir="rtl" e side="right". Espaçamento, linhas de submenu, tooltips e o ícone do gatilho são espelhados.

import { IconHome, IconInbox, IconSettings } from "@tabler/icons-react"

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarInset,
  SidebarMenu,
  SidebarMenuBadge,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
} from "@/components/ui/sidebar"

const items = [
  { title: "الرئيسية", icon: IconHome },
  { title: "البريد الوارد", icon: IconInbox, badge: "٢٤" },
  { title: "الإعدادات", icon: IconSettings },
]

export function SidebarRtl() {
  return (
    <SidebarProvider dir="rtl" className="h-96 min-h-0">
      <Sidebar side="right" collapsible="icon" dir="rtl">
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupLabel>التطبيق</SidebarGroupLabel>
            <SidebarGroupContent>
              <SidebarMenu>
                {items.map((item, index) => (
                  <SidebarMenuItem key={item.title}>
                    <SidebarMenuButton
                      isActive={index === 0}
                      tooltip={item.title}
                    >
                      <item.icon />
                      <span>{item.title}</span>
                    </SidebarMenuButton>
                    {item.badge && (
                      <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>
                    )}
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
        <SidebarRail />
      </Sidebar>
      <SidebarInset>
        <header className="flex h-14 items-center px-4">
          <SidebarTrigger className="-ms-1" />
        </header>
      </SidebarInset>
    </SidebarProvider>
  )
}

A sidebar tem 16rem de largura, 18rem em celulares e 3rem quando reduzida a ícones. Sobrescreva --sidebar-width, --sidebar-width-mobile e --sidebar-width-icon no provider.

<SidebarProvider
  style={
    {
      "--sidebar-width": "20rem",
      "--sidebar-width-mobile": "20rem",
    } as React.CSSProperties
  }
>

A sidebar não tem efeitos colaterais. Para lembrar se estava aberta, salve em onOpenChange e leia de volta em defaultOpen ao renderizar no servidor.

"use client"

export function Providers({
  defaultOpen,
  children,
}: {
  defaultOpen: boolean
  children: React.ReactNode
}) {
  return (
    <SidebarProvider
      defaultOpen={defaultOpen}
      onOpenChange={(open) => {
        document.cookie = `sidebar_state=${open}; path=/; max-age=604800`
      }}
    >
      {children}
    </SidebarProvider>
  )
}
TeclaAção
⌘ + BCtrl + BAlterna a sidebar. Com várias sidebars em uma página, responde a que tem o foco, senão a primeira. Ignorado ao digitar em um editor de rich text.
TabShift + TabPercorre os links. Uma sidebar recolhida fora da tela é ignorada.
EnterSpaceAtiva o link, botão ou gatilho com foco.
EscFecha a sidebar em celulares.
  • SidebarTrigger é rotulado “Toggle Sidebar” e expõe aria-expanded e aria-controls.
  • isActive define aria-current="page".
  • Recolher para fora da tela oculta o conteúdo do teclado e dos leitores de tela. Se o foco estava dentro, ele vai para o gatilho.
  • Reduzida a ícones, os rótulos permanecem no nome acessível de cada link e aparecem como tooltip no hover e no foco do teclado. Rótulos de grupo, ações e badges ficam ocultos.
  • Em celulares a sidebar é um sheet modal: o foco fica preso, deslizar ou Esc a fecha, e seguir um link a fecha. Links que abrem uma nova aba, downloads e cliques com modificadores a mantêm aberta.
  • Com movimento reduzido ativado, o recolhimento é instantâneo.
PropTipoPadrão
defaultOpen
booleantrue
open
boolean–
onOpenChange
(open: boolean) => void–
keyboardShortcutmod é ⌘ em plataformas Apple e Ctrl nas demais. null desativa.
string | null"mod+b"
AtributoDescrição
data-slot="sidebar-wrapper"O wrapper de layout.
--sidebar-widthLargura expandida. O padrão é 16rem.
--sidebar-width-mobileLargura do sheet do celular. O padrão é 18rem.
--sidebar-width-iconLargura quando reduzida a ícones. O padrão é 3rem.
--sidebar-topOnde a sidebar fixa ao rolar, como abaixo de um cabeçalho sticky. O padrão é 0px.

className e outras props vão para o contêiner da sidebar.

PropTipoPadrão
side
"left" | "right""left"
variantplain remove a superfície, a linha da borda, a barra de rolagem e o espaço entre os itens do menu, para uma sidebar que fica sobre a página.
"sidebar" | "floating" | "inset" | "plain""sidebar"
collapsible
"offcanvas" | "icon" | "none""offcanvas"
mobileComo abre em celulares: um sheet pela lateral, ou deslizando para preencher a tela, como nos apps de chat.
"sheet" | "fullscreen""sheet"
dirTambém define a direção do sheet no celular.
"ltr" | "rtl"–
AtributoDescrição
data-slot="sidebar"A sidebar, ou o sheet em celulares.
data-state"expanded" ou "collapsed".
data-collapsibleO modo recolhível enquanto recolhida, senão "". Estilize os filhos com group-data-[collapsible=icon]:.
data-variantA variante.
data-sideO lado.
data-mobilePresente no sheet do celular.
data-slot="sidebar-container"O painel que desliza e se redimensiona.
data-slot="sidebar-inner"A superfície que contém o conteúdo.

Um Button ghost com ícone que chama toggleSidebar. Chame event.preventDefault() em onClick para impedir. Passe children para substituir o ícone.

AtributoDescrição
data-slot="sidebar-trigger"O gatilho.
aria-expandedSe a sidebar está aberta.

Uma área de toque fina na borda da sidebar que a alterna ao clicar. Fica fora da ordem de tabulação porque o gatilho e o atalho atendem usuários de teclado. Com a sidebar fora da tela, o rail permanece na borda da tela.

AtributoDescrição
data-slot="sidebar-rail"O rail.

Um <main> que ocupa o resto da largura. Ao lado de uma sidebar inset, vira um card arredondado.

PropTipoPadrão
renderRenderiza um elemento diferente. Passe um <div /> quando a página já tem um landmark <main>.
React.ReactElement | (props) => React.ReactElement–
AtributoDescrição
data-slot="sidebar-inset"A área da página.

Elementos <div> simples. O conteúdo rola com uma barra de rolagem fina e esmaecimentos suaves nas bordas.

AtributoDescrição
data-slot="sidebar-header"Seção superior.
data-slot="sidebar-content"Meio rolável.
data-slot="sidebar-footer"Seção inferior.
AtributoDescrição
data-slot="sidebar-group"Uma seção da sidebar.
data-slot="sidebar-group-content"O conteúdo do grupo.
PropTipoPadrão
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescrição
data-slot="sidebar-group-label"Sobe e some com fade quando reduzida a ícones.
PropTipoPadrão
render
ReactElement | (props, state) => ReactElement<button>
AtributoDescrição
data-slot="sidebar-group-action"Precisa de um rótulo acessível.

Um <ul> e seus itens <li>. Defina gap="none" para listas densas como histórico de chat, onde as linhas ficam coladas.

PropTipoPadrão
gapEspaço entre os itens. Também em SidebarMenuSub.
"default" | "none""default"
AtributoDescrição
data-slot="sidebar-menu"A lista.
data-slot="sidebar-menu-item"Um item. group/menu-item para estilos de hover.
PropTipoPadrão
isActiveDestaca-o e define aria-current="page".
booleanfalse
variant
"default" | "outline""default"
size
"default" | "sm" | "lg""default"
tooltipExibido quando reduzida a ícones. O conteúdo desliza entre os itens conforme você percorre o menu.
ReactNode | TooltipContentProps–
render
ReactElement | (props, state) => ReactElement<button>
AtributoDescrição
data-slot="sidebar-menu-button"peer/menu-button para estilos de irmãos.
data-activePresente enquanto isActive.
data-sizeO tamanho.
PropTipoPadrão
showOnHoverMostra só enquanto o item está com hover ou foco, ou seu menu está aberto. Sempre visível em telas touch.
booleanfalse
render
ReactElement | (props, state) => ReactElement<button>
AtributoDescrição
data-slot="sidebar-menu-action"Precisa de um rótulo acessível.
AtributoDescrição
data-slot="sidebar-menu-badge"Uma contagem no fim do item.
PropTipoPadrão
showIcon
booleanfalse
AtributoDescrição
data-slot="sidebar-menu-skeleton"Oculto das tecnologias assistivas.

Uma lista aninhada com uma linha na borda inicial. Ela se recolhe quando a sidebar é reduzida a ícones.

PropTipoPadrão
isActiveEm SidebarMenuSubButton.
booleanfalse
sizeEm SidebarMenuSubButton.
"sm" | "md""md"
renderEm SidebarMenuSubButton.
ReactElement | (props, state) => ReactElement<a>
AtributoDescrição
data-slot="sidebar-menu-sub"A lista aninhada.
data-slot="sidebar-menu-sub-button"Um link aninhado.
data-activePresente enquanto isActive.

Um Input pequeno sobre o fundo da página e um separador de linha fina.

AtributoDescrição
data-slot="sidebar-input"O input.
data-slot="sidebar-separator"O separador.

Lê e controla a sidebar mais próxima. Lança erro fora de SidebarProvider.

const {
  state,
  open,
  setOpen,
  openMobile,
  setOpenMobile,
  isMobile,
  toggleSidebar,
} = useSidebar()
PropTipoPadrão
state
"expanded" | "collapsed"–
open
boolean–
setOpen
(open: boolean) => void–
openMobile
boolean–
setOpenMobile
(open: boolean) => void–
isMobile
boolean–
toggleSidebarAlterna o sheet em celulares, senão a sidebar.
() => void–

Usado em blocos

Blocos que se baseiam em Sidebar.