HextaUI

Sidebar

Une barre latérale d’application qui se réduit en icônes ou hors canevas, reste épinglée sous votre en-tête et devient une sheet balayable sur mobile.

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

Ajoute le composant, les tokens de thème HextaUI et les composants HextaUI dont il dépend.

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

Entourez votre mise en page de <SidebarProvider /> et placez la page dans <SidebarInset />, après la 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 contient l'état d'ouverture, le raccourci clavier et les largeurs.
  • Sidebar est une colonne qui reste fixée pendant le défilement de la page. En dessous de 768px, elle devient une sheet.
  • SidebarHeader et SidebarFooter restent en place. SidebarContent défile entre les deux.
  • SidebarGroup est une section avec un label et une action facultatifs. SidebarMenu contient les liens.
  • SidebarInset est la page à côté de la sidebar.

Variantes

variant définit l'apparence : une sidebar pleine hauteur, un panneau floating, ou inset, où la page devient une carte sur la couleur de la sidebar. Passez de l'une à l'autre pour voir la mise en page s'animer.

"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 fait glisser la sidebar hors de la vue, icon la réduit à ses icônes et affiche chaque label dans un tooltip, et none la garde ouverte. Appuyez sur ⌘B ou Ctrl+B pour basculer la sidebar dans laquelle vous travaillez.

"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>
  )
}

Groupes repliables et sous-menus

Entourez un SidebarGroup ou un SidebarMenuItem d'un Collapsible, rendez le label ou le bouton comme déclencheur, et imbriquez un 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>
  )
}

Côté droit

Définissez side="right" et placez la sidebar après SidebarInset. Le rail et la sheet mobile suivent le côté.

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>
  )
}

Sous un en-tête

La sidebar est sticky : elle démarre donc sous tout ce qui la précède. Avec un en-tête sticky, définissez --sidebar-top à sa hauteur et la sidebar se fixe en dessous et occupe le reste de l'écran.

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>
  )
}

Contrôlé

Passez open et onOpenChange. Le déclencheur, le rail et le raccourci passent tous par 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>
  )
}

Chargement

SidebarMenuSkeleton remplit un menu pendant son chargement. Les largeurs varient selon la ligne et sont identiques entre serveur et client.

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>
  )
}

De droite à gauche

Définissez dir="rtl" et side="right". L'espacement, les traits des sous-menus, les tooltips et l'icône du déclencheur sont inversés.

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>
  )
}

La sidebar fait 16rem de large, 18rem sur téléphone et 3rem réduite aux icônes. Remplacez --sidebar-width, --sidebar-width-mobile et --sidebar-width-icon sur le provider.

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

La sidebar n'a aucun effet de bord. Pour mémoriser si elle était ouverte, enregistrez l'état dans onOpenChange et relisez-le dans defaultOpen lors du rendu côté serveur.

"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>
  )
}
ToucheAction
⌘ + BCtrl + BBascule la sidebar. Avec plusieurs sidebars sur une page, celle qui a le focus répond, sinon la première. Ignoré pendant la saisie dans un éditeur de texte riche.
TabShift + TabParcourt les liens. Une sidebar repliée hors canevas est ignorée.
EnterSpaceActive le lien, bouton ou déclencheur qui a le focus.
EscFerme la sidebar sur téléphone.
  • SidebarTrigger a le label « Toggle Sidebar » et expose aria-expanded et aria-controls.
  • isActive définit aria-current="page".
  • Replier hors canevas masque le contenu au clavier et aux lecteurs d'écran. Si le focus était à l'intérieur, il passe au déclencheur.
  • Réduite aux icônes, les labels restent dans le nom accessible de chaque lien et s'affichent en tooltip au survol et au focus clavier. Les labels de groupe, actions et badges sont masqués.
  • Sur téléphone, la sidebar est une sheet modale : le focus est piégé, un balayage ou Esc la ferme, et suivre un lien la ferme. Les liens qui ouvrent un nouvel onglet, les téléchargements et les clics avec modificateur la laissent ouverte.
  • Avec la réduction des animations, le repli est instantané.
PropTypePar défaut
defaultOpen
booleantrue
open
boolean–
onOpenChange
(open: boolean) => void–
keyboardShortcutmod est ⌘ sur les plateformes Apple et Ctrl ailleurs. null le désactive.
string | null"mod+b"
AttributDescription
data-slot="sidebar-wrapper"Le wrapper de mise en page.
--sidebar-widthLargeur déployée. 16rem par défaut.
--sidebar-width-mobileLargeur de la sheet du téléphone. 18rem par défaut.
--sidebar-width-iconLargeur réduite aux icônes. 3rem par défaut.
--sidebar-topOù la sidebar se fixe pendant le défilement, par exemple sous un en-tête sticky. 0px par défaut.

className et les autres props vont au conteneur de la sidebar.

PropTypePar défaut
side
"left" | "right""left"
variantplain supprime la surface, le trait de bord, la barre de défilement et l'espace entre les éléments du menu, pour une sidebar posée sur la page.
"sidebar" | "floating" | "inset" | "plain""sidebar"
collapsible
"offcanvas" | "icon" | "none""offcanvas"
mobileComment elle s'ouvre sur téléphone : une sheet latérale, ou un glissement qui remplit l'écran, comme les applications de chat.
"sheet" | "fullscreen""sheet"
dirDéfinit aussi la direction de la sheet sur téléphone.
"ltr" | "rtl"–
AttributDescription
data-slot="sidebar"La sidebar, ou la sheet sur téléphone.
data-state"expanded" ou "collapsed".
data-collapsibleLe mode repliable quand elle est repliée, sinon "". Stylez les enfants avec group-data-[collapsible=icon]:.
data-variantLa variante.
data-sideLe côté.
data-mobilePrésent sur la sheet du téléphone.
data-slot="sidebar-container"Le panneau qui glisse et se redimensionne.
data-slot="sidebar-inner"La surface qui contient le contenu.

Un Button icône ghost qui appelle toggleSidebar. Appelez event.preventDefault() dans onClick pour l'arrêter. Passez des enfants pour remplacer l'icône.

AttributDescription
data-slot="sidebar-trigger"Le déclencheur.
aria-expandedIndique si la sidebar est ouverte.

Une fine zone de clic sur le bord de la sidebar qui la bascule au clic. Elle est exclue de l'ordre de tabulation car le déclencheur et le raccourci couvrent les utilisateurs du clavier. Quand la sidebar est hors canevas, le rail reste au bord de l'écran.

AttributDescription
data-slot="sidebar-rail"Le rail.

Un <main> qui prend le reste de la largeur. À côté d'une sidebar inset, il devient une carte arrondie.

PropTypePar défaut
renderRend un autre élément. Passez un <div /> quand la page a déjà un repère <main>.
React.ReactElement | (props) => React.ReactElement–
AttributDescription
data-slot="sidebar-inset"La zone de la page.

Des éléments <div> simples. Le contenu défile avec une fine barre de défilement et de doux fondus de bord.

AttributDescription
data-slot="sidebar-header"Section du haut.
data-slot="sidebar-content"Milieu défilant.
data-slot="sidebar-footer"Section du bas.
AttributDescription
data-slot="sidebar-group"Une section de la sidebar.
data-slot="sidebar-group-content"Le contenu du groupe.
PropTypePar défaut
render
ReactElement | (props, state) => ReactElement<div>
AttributDescription
data-slot="sidebar-group-label"Glisse vers le haut et s'estompe quand elle est réduite aux icônes.
PropTypePar défaut
render
ReactElement | (props, state) => ReactElement<button>
AttributDescription
data-slot="sidebar-group-action"Nécessite un label accessible.

Un <ul> et ses éléments <li>. Définissez gap="none" pour des listes denses comme l'historique de chat, où les lignes se touchent.

PropTypePar défaut
gapEspace entre les éléments. Aussi sur SidebarMenuSub.
"default" | "none""default"
AttributDescription
data-slot="sidebar-menu"La liste.
data-slot="sidebar-menu-item"Un élément. group/menu-item pour les styles au survol.
PropTypePar défaut
isActiveLe met en évidence et définit aria-current="page".
booleanfalse
variant
"default" | "outline""default"
size
"default" | "sm" | "lg""default"
tooltipAffiché quand elle est réduite aux icônes. Le contenu glisse d'un élément à l'autre quand vous parcourez le menu.
ReactNode | TooltipContentProps–
render
ReactElement | (props, state) => ReactElement<button>
AttributDescription
data-slot="sidebar-menu-button"peer/menu-button pour les styles de frères.
data-activePrésent tant que isActive est vrai.
data-sizeLa taille.
PropTypePar défaut
showOnHoverNe l'affiche que tant que l'élément est survolé ou focalisé, ou que son menu est ouvert. Toujours visible sur écran tactile.
booleanfalse
render
ReactElement | (props, state) => ReactElement<button>
AttributDescription
data-slot="sidebar-menu-action"Nécessite un label accessible.
AttributDescription
data-slot="sidebar-menu-badge"Un compteur à la fin de l'élément.
PropTypePar défaut
showIcon
booleanfalse
AttributDescription
data-slot="sidebar-menu-skeleton"Masqué aux technologies d'assistance.

Une liste imbriquée avec un trait sur son bord de début. Elle se replie quand la sidebar se réduit aux icônes.

PropTypePar défaut
isActiveSur SidebarMenuSubButton.
booleanfalse
sizeSur SidebarMenuSubButton.
"sm" | "md""md"
renderSur SidebarMenuSubButton.
ReactElement | (props, state) => ReactElement<a>
AttributDescription
data-slot="sidebar-menu-sub"La liste imbriquée.
data-slot="sidebar-menu-sub-button"Un lien imbriqué.
data-activePrésent tant que isActive est vrai.

Un petit Input sur l'arrière-plan de la page, et un séparateur en filet.

AttributDescription
data-slot="sidebar-input"Le champ.
data-slot="sidebar-separator"Le séparateur.

Lit et contrôle la sidebar la plus proche. Lève une erreur en dehors de SidebarProvider.

const {
  state,
  open,
  setOpen,
  openMobile,
  setOpenMobile,
  isMobile,
  toggleSidebar,
} = useSidebar()
PropTypePar défaut
state
"expanded" | "collapsed"–
open
boolean–
setOpen
(open: boolean) => void–
openMobile
boolean–
setOpenMobile
(open: boolean) => void–
isMobile
boolean–
toggleSidebarBascule la sheet sur téléphone, sinon la sidebar.
() => void–

Utilisé dans les blocks

Des blocks qui s’appuient sur Sidebar.