Sidebar
Stała nawigacja aplikacji — zwija się do paska ikon, na telefonach wysuwa się spoza ekranu, a jej wygląd dopasujesz do motywu co do piksela.
import {
CalendarIcon,
ChevronsUpDownIcon,
FolderKanbanIcon,
HouseIcon,
InboxIcon,
SearchIcon,
} from "lucide-react"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
} from "@/components/ui/sidebar"
const navigation = [
{ title: "Strona główna", icon: HouseIcon, active: true },
{ title: "Skrzynka odbiorcza", icon: InboxIcon, badge: 12 },
{ title: "Projekty", icon: FolderKanbanIcon },
{ title: "Kalendarz", icon: CalendarIcon },
{ title: "Szukaj", icon: SearchIcon },
]
const favorites = [
{ title: "Design system", color: "bg-brand-9" },
{ title: "Premiera w Q4", color: "bg-amber-9" },
{ title: "Badania klientów", color: "bg-teal-9" },
]
export default function SidebarDemo() {
return (
<SidebarProvider className="min-h-0 w-fit">
<Sidebar
collapsible="none"
className="h-104 rounded-xl border shadow-raised"
>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg">
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-foreground text-sm font-semibold text-background">
A
</span>
<span className="flex min-w-0 flex-col gap-0.5 leading-none">
<span className="truncate font-semibold text-foreground">
Acme Inc
</span>
<span className="truncate text-xs text-muted-foreground">
Plan Pro
</span>
</span>
<ChevronsUpDownIcon className="ml-auto" />
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{navigation.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton isActive={item.active}>
<item.icon />
<span>{item.title}</span>
</SidebarMenuButton>
{item.badge !== undefined && (
<SidebarMenuBadge>{item.badge}</SidebarMenuBadge>
)}
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupLabel>Ulubione</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{favorites.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton>
<span
className={`ml-1 size-2 shrink-0 rounded-full ${item.color}`}
/>
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg">
<Avatar size="sm">
<AvatarFallback>AK</AvatarFallback>
</Avatar>
<span className="flex min-w-0 flex-col gap-0.5 leading-none">
<span className="truncate font-medium text-foreground">
Ana Kowalska
</span>
<span className="truncate text-xs text-muted-foreground">
ana@acme.com
</span>
</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
</SidebarProvider>
)
}Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/sidebar.jsonUżycie
Pasek boczny należy do układu aplikacji, a nie do strony. Owiń układ w SidebarProvider, wyrenderuj Sidebar obok SidebarInset, który zawiera stronę, i umieść SidebarTrigger wszędzie tam, skąd ludzie mają móc zwinąć i rozwinąć pasek.
import { cookies } from "next/headers"
import { AppSidebar } from "@/components/app-sidebar"
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar"
export default async function AppLayout({ children }: { children: React.ReactNode }) {
// The provider saves its state in a cookie; read it to render the same state on the server.
const cookieStore = await cookies()
const defaultOpen = cookieStore.get("sidebar_state")?.value !== "false"
return (
<SidebarProvider defaultOpen={defaultOpen}>
<AppSidebar />
<SidebarInset>
<header className="flex h-12 items-center gap-2 border-b px-3">
<SidebarTrigger />
</header>
{children}
</SidebarInset>
</SidebarProvider>
)
}"use client"
import { HouseIcon, InboxIcon, SettingsIcon } from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
} from "@/components/ui/sidebar"
const items = [
{ title: "Home", href: "/", icon: HouseIcon },
{ title: "Inbox", href: "/inbox", icon: InboxIcon },
{ title: "Settings", href: "/settings", icon: SettingsIcon },
]
export function AppSidebar() {
const pathname = usePathname()
return (
<Sidebar collapsible="icon">
<SidebarContent>
<SidebarGroup>
<SidebarGroupContent>
<nav aria-label="Main">
<SidebarMenu>
{items.map((item) => {
const active = pathname === item.href
return (
<SidebarMenuItem key={item.href}>
<SidebarMenuButton
isActive={active}
tooltip={item.title}
render={
<Link href={item.href} aria-current={active ? "page" : undefined} />
}
>
<item.icon />
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
)
})}
</SidebarMenu>
</nav>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarRail />
</Sidebar>
)
}Anatomia
SidebarProvider state, keyboard shortcut, width variables
├─ Sidebar the panel (desktop) or a sheet (mobile)
│ ├─ SidebarHeader workspace switcher, search
│ ├─ SidebarContent scrolls
│ │ └─ SidebarGroup
│ │ ├─ SidebarGroupLabel · SidebarGroupAction
│ │ └─ SidebarGroupContent
│ │ └─ SidebarMenu
│ │ └─ SidebarMenuItem
│ │ ├─ SidebarMenuButton · SidebarMenuAction · SidebarMenuBadge
│ │ └─ SidebarMenuSub → SidebarMenuSubItem → SidebarMenuSubButton
│ ├─ SidebarFooter account, help
│ └─ SidebarRail a clickable edge that toggles the sidebar
└─ SidebarInset the page, with SidebarTrigger in its headerPrzykłady
Poniższe podglądy to prawdziwe paski boczne zamknięte w ramce. Na desktopie pasek boczny ma position: fixed; podgląd nadaje swojemu kontenerowi transform: translateZ(0), przez co kontener staje się blokiem zawierającym dla paska, i renderuje SidebarInset jako <div>, bo strona dokumentacji ma już <main>. W twojej aplikacji nie potrzebujesz ani jednego, ani drugiego.
Powłoka aplikacji
collapsible="icon" z SidebarRail i SidebarTrigger. Kliknij wyzwalacz, kliknij szynę wzdłuż prawej krawędzi paska albo naciśnij ⌘ B, aby zwinąć go do paska ikon.
import {
BookOpenIcon,
ChartBarIcon,
FolderKanbanIcon,
HouseIcon,
InboxIcon,
LifeBuoyIcon,
SettingsIcon,
} from "lucide-react"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import { Separator } from "@/components/ui/separator"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/components/ui/sidebar"
const platform = [
{ title: "Strona główna", icon: HouseIcon },
{ title: "Skrzynka odbiorcza", icon: InboxIcon },
{ title: "Projekty", icon: FolderKanbanIcon, active: true },
{ title: "Analityka", icon: ChartBarIcon },
]
const support = [
{ title: "Dokumentacja", icon: BookOpenIcon },
{ title: "Centrum pomocy", icon: LifeBuoyIcon },
{ title: "Ustawienia", icon: SettingsIcon },
]
export default function SidebarAppShell() {
return (
// `transform` makes this box the containing block for the sidebar's fixed
// positioning, so the whole app shell fits inside the preview.
<SidebarProvider className="h-120 min-h-0 [transform:translateZ(0)] overflow-hidden rounded-xl border">
<Sidebar collapsible="icon" className="h-auto">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" tooltip="Acme Inc">
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-foreground text-sm font-semibold text-background">
A
</span>
<span className="flex min-w-0 flex-col gap-0.5 leading-none">
<span className="truncate font-semibold text-foreground">
Acme Inc
</span>
<span className="truncate text-xs text-muted-foreground">
Plan Pro
</span>
</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platforma</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{platform.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
isActive={item.active}
tooltip={item.title}
>
<item.icon />
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup className="mt-auto">
<SidebarGroupLabel>Pomoc</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{support.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton size="sm" tooltip={item.title}>
<item.icon />
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" tooltip="Ana Kowalska">
<Avatar size="sm">
<AvatarFallback>AK</AvatarFallback>
</Avatar>
<span className="flex min-w-0 flex-col gap-0.5 leading-none">
<span className="truncate font-medium text-foreground">
Ana Kowalska
</span>
<span className="truncate text-xs text-muted-foreground">
ana@acme.com
</span>
</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
<SidebarRail />
</Sidebar>
{/* The docs page already has a <main>, so the preview renders a <div>. */}
<SidebarInset render={<div />}>
<header className="flex h-12 shrink-0 items-center gap-2 border-b px-3">
<SidebarTrigger />
<Separator
orientation="vertical"
className="mr-1 h-4 data-vertical:self-center"
/>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="#">Projekty</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Design system</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</header>
<div className="grid flex-1 auto-rows-min gap-3 p-4 sm:grid-cols-3">
<div className="aspect-video rounded-lg bg-muted" />
<div className="aspect-video rounded-lg bg-muted" />
<div className="aspect-video rounded-lg bg-muted" />
<div className="h-40 rounded-lg bg-muted sm:col-span-3" />
</div>
</SidebarInset>
</SidebarProvider>
)
}Zwinięty do ikon
Po zwinięciu etykiety są przycinane, a każdy przycisk pokazuje swój tooltip po najechaniu i przy fokusie. Jeśli korzystasz z tego trybu, daj każdemu elementowi najwyższego poziomu ikonę i podpowiedź.
import {
ChartBarIcon,
FolderKanbanIcon,
HouseIcon,
InboxIcon,
SettingsIcon,
} from "lucide-react"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar"
const items = [
{ title: "Strona główna", icon: HouseIcon, active: true },
{ title: "Skrzynka odbiorcza", icon: InboxIcon },
{ title: "Projekty", icon: FolderKanbanIcon },
{ title: "Analityka", icon: ChartBarIcon },
]
export default function SidebarIconCollapsed() {
return (
<SidebarProvider
defaultOpen={false}
className="h-80 min-h-0 [transform:translateZ(0)] overflow-hidden rounded-xl border"
>
<Sidebar collapsible="icon" className="h-auto">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" tooltip="Acme Inc">
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-foreground text-sm font-semibold text-background">
A
</span>
<span className="truncate font-semibold text-foreground">
Acme Inc
</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
isActive={item.active}
tooltip={item.title}
>
<item.icon />
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton tooltip="Ustawienia">
<SettingsIcon />
<span>Ustawienia</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
<SidebarInset render={<div />}>
<header className="flex h-12 shrink-0 items-center gap-2 border-b px-3">
<SidebarTrigger />
<span className="text-sm font-medium text-foreground">Strona główna</span>
</header>
<div className="flex flex-1 flex-col gap-3 p-4">
<div className="h-24 rounded-lg bg-muted" />
<div className="flex-1 rounded-lg bg-muted" />
</div>
</SidebarInset>
</SidebarProvider>
)
}Warianty
sidebar przylega do strony i oddziela się od niej obramowaniem. floating zamienia się w wyniesioną kartę odsuniętą od krawędzi. inset wtapia się w tło strony, a treść leży na wyniesionym panelu — to najspokojniejsza opcja dla gęstych aplikacji.
"use client"
import * as React from "react"
import { FolderKanbanIcon, HouseIcon, InboxIcon } from "lucide-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: "Strona główna", icon: HouseIcon, active: true },
{ title: "Skrzynka odbiorcza", icon: InboxIcon },
{ title: "Projekty", icon: FolderKanbanIcon },
]
export default function SidebarVariants() {
const [variant, setVariant] = React.useState<Variant>("inset")
return (
<div className="flex w-full flex-col items-center gap-4">
<ToggleGroup
variant="outline"
size="sm"
spacing={0}
aria-label="Wariant paska bocznego"
value={[variant]}
onValueChange={(value) => value[0] && setVariant(value[0] as Variant)}
>
<ToggleGroupItem value="sidebar">Sidebar</ToggleGroupItem>
<ToggleGroupItem value="floating">Floating</ToggleGroupItem>
<ToggleGroupItem value="inset">Inset</ToggleGroupItem>
</ToggleGroup>
<SidebarProvider className="h-80 min-h-0 w-full [transform:translateZ(0)] overflow-hidden rounded-xl border">
<Sidebar variant={variant} collapsible="icon" className="h-auto">
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Przestrzeń robocza</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
isActive={item.active}
tooltip={item.title}
>
<item.icon />
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SidebarInset render={<div />}>
<header className="flex h-12 shrink-0 items-center gap-2 border-b px-3">
<SidebarTrigger />
<span className="text-sm font-medium text-foreground capitalize">
{variant}
</span>
</header>
<div className="flex flex-1 flex-col gap-3 p-4">
<div className="h-20 rounded-lg bg-muted" />
<div className="flex-1 rounded-lg bg-muted" />
</div>
</SidebarInset>
</SidebarProvider>
</div>
)
}Zwijane sekcje
Połącz Collapsible z menu, aby zbudować rozwijane sekcje. Collapsible renderuje SidebarMenuItem, a jego wyzwalacz — SidebarMenuButton, więc nie ma żadnych dodatkowych elementów opakowujących.
import {
BookOpenIcon,
ChevronRightIcon,
LayersIcon,
PaletteIcon,
} from "lucide-react"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
} from "@/components/ui/sidebar"
const sections = [
{
title: "Pierwsze kroki",
icon: BookOpenIcon,
open: true,
items: ["Wprowadzenie", "Instalacja", "Zasady"],
active: "Instalacja",
},
{
title: "Fundamenty",
icon: PaletteIcon,
items: ["Kolor", "Typografia", "Ruch"],
},
{
title: "Komponenty",
icon: LayersIcon,
items: ["Button", "Dialog", "Sidebar"],
},
]
export default function SidebarSubmenu() {
return (
<SidebarProvider className="min-h-0 w-fit">
<Sidebar
collapsible="none"
className="h-96 rounded-xl border shadow-raised"
>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Dokumentacja</SidebarGroupLabel>
<SidebarMenu>
{sections.map((section) => (
<Collapsible
key={section.title}
defaultOpen={section.open}
render={<SidebarMenuItem />}
className="group/collapsible"
>
<CollapsibleTrigger render={<SidebarMenuButton />}>
<section.icon />
<span>{section.title}</span>
<ChevronRightIcon className="ml-auto transition-transform duration-base ease-standard group-data-open/collapsible:rotate-90" />
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{section.items.map((item) => (
<SidebarMenuSubItem key={item}>
<SidebarMenuSubButton
render={<button type="button" />}
isActive={item === section.active}
className="w-full"
>
<span>{item}</span>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</Collapsible>
))}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
)
}<Collapsible defaultOpen render={<SidebarMenuItem />} className="group/collapsible">
<CollapsibleTrigger render={<SidebarMenuButton />}>
<BookOpenIcon />
<span>Get started</span>
<ChevronRightIcon className="ml-auto transition-transform group-data-open/collapsible:rotate-90" />
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>…</SidebarMenuSub>
</CollapsibleContent>
</Collapsible>Akcje i odznaki
SidebarMenuBadge pokazuje licznik na końcu elementu. SidebarMenuAction dodaje akcję pomocniczą — często menu — która z showOnHover pojawia się dopiero po najechaniu. SidebarGroupAction dodaje akcję do etykiety grupy, na przykład Dodaj projekt.
import {
ArchiveIcon,
InboxIcon,
MoreHorizontalIcon,
PencilIcon,
PlusIcon,
SendIcon,
Trash2Icon,
} from "lucide-react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
} from "@/components/ui/sidebar"
const mail = [
{ title: "Odebrane", icon: InboxIcon, count: 24 },
{ title: "Wysłane", icon: SendIcon },
{ title: "Archiwum", icon: ArchiveIcon, count: 3 },
]
const projects = [
{ title: "Design system", color: "bg-brand-9" },
{ title: "Strona marketingowa", color: "bg-pink-9" },
{ title: "Aplikacja mobilna", color: "bg-green-9" },
]
export default function SidebarActions() {
return (
<SidebarProvider className="min-h-0 w-fit">
<Sidebar
collapsible="none"
className="h-88 rounded-xl border shadow-raised"
>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Poczta</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{mail.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton isActive={item.title === "Odebrane"}>
<item.icon />
<span>{item.title}</span>
</SidebarMenuButton>
{item.count !== undefined && (
<SidebarMenuBadge>{item.count}</SidebarMenuBadge>
)}
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupLabel>Projekty</SidebarGroupLabel>
<SidebarGroupAction title="Dodaj projekt">
<PlusIcon />
<span className="sr-only">Dodaj projekt</span>
</SidebarGroupAction>
<SidebarGroupContent>
<SidebarMenu>
{projects.map((project) => (
<SidebarMenuItem key={project.title}>
<SidebarMenuButton>
<span
className={`ml-1 size-2 shrink-0 rounded-full ${project.color}`}
/>
<span>{project.title}</span>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger
render={<SidebarMenuAction showOnHover />}
>
<MoreHorizontalIcon />
<span className="sr-only">
Więcej akcji dla projektu {project.title}
</span>
</DropdownMenuTrigger>
<DropdownMenuContent
side="right"
align="start"
className="w-44"
>
<DropdownMenuGroup>
<DropdownMenuItem>
<PencilIcon />
Zmień nazwę
</DropdownMenuItem>
<DropdownMenuItem>
<ArchiveIcon />
Archiwum
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<Trash2Icon />
Usuń
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
)
}Ładowanie
Renderuj SidebarMenuSkeleton, dopóki elementy się ładują. Szerokości różnią się w każdym wierszu, ale wynikają z useId, więc serwer i klient renderują ten sam HTML.
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarProvider,
} from "@/components/ui/sidebar"
export default function SidebarSkeleton() {
return (
<SidebarProvider className="min-h-0 w-fit">
<Sidebar
collapsible="none"
className="h-72 rounded-xl border shadow-raised"
>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Projekty</SidebarGroupLabel>
<SidebarMenu aria-busy="true" aria-label="Ładowanie projektów">
{Array.from({ length: 6 }, (_, index) => (
<SidebarMenuItem key={index}>
<SidebarMenuSkeleton showIcon />
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
)
}Tryby zwijania
Na ekranach węższych niż 768 px pasek boczny w trybie offcanvas lub icon staje się panelem Sheet, który wysuwa się od strony wskazanej w side; otwiera go SidebarTrigger. Pasek w trybie none pozostaje w układzie przy każdej szerokości, więc na małych ekranach samodzielnie go ukryj albo zmień jego wygląd.
Stan kontrolowany
Steruj propem open, aby zsynchronizować pasek boczny z własnym stanem, a wszystko, czego potrzebujesz, odczytasz z useSidebar() w dowolnym miejscu wewnątrz providera.
const [open, setOpen] = React.useState(true)
<SidebarProvider open={open} onOpenChange={setOpen}>…</SidebarProvider>const { state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar } = useSidebar()Szerokość i strona
Szerokości to zmienne CSS ustawione na providerze — --sidebar-width (16 rem) i --sidebar-width-icon (3 rem). Przekaż side="right", aby umieścić pasek przy przeciwnej krawędzi, na przykład jako panel inspektora.
<SidebarProvider
style={{ "--sidebar-width": "18rem", "--sidebar-width-icon": "3.5rem" } as React.CSSProperties}
>
<Sidebar side="right" variant="floating">…</Sidebar>
</SidebarProvider>Dostosowywanie motywu
Pasek boczny ma własne tokeny semantyczne, więc może różnić się od strony bez zmian w komponentach.
Wytyczne
Kiedy używać
- W aplikacjach, które mają więcej niż pięć głównych miejsc docelowych albo miejsca, między którymi ludzie przechodzą przez cały dzień.
- Gdy nawigacja obejmuje zagnieżdżone sekcje, projekty lub zapisane widoki, które muszą być w zasięgu jednego kliknięcia.
Kiedy nie używać
- Przy kilku miejscach docelowych — nagłówek z Tabs albo Navigation Menu zajmie mniej miejsca.
- Na stronach marketingowych i w dokumentacji, gdzie treść zasługuje na całą szerokość.
- Jako miejsce na ustawienia lub formularze — użyj komponentu Sheet albo osobnej strony.
Porządkuj według tego, co ludzie robią
Grupuj elementy według zadań, nadaj każdej grupie etykietę i ogranicz etykiety do jednego lub dwóch słów. Przełącznik przestrzeni roboczej lub konta umieść w nagłówku, a pomoc, ustawienia i profil — w stopce, żeby środek zawierał wyłącznie miejsca docelowe.
Pokazuj bieżące położenie
Oznacz jako bieżący dokładnie jeden element za pomocą isActive. Właściwość stylizuje element i ustawia na nim aria-current="page", żeby technologie wspomagające również to przekazały. W zagnieżdżonych sekcjach rozwiń tę, która zawiera bieżącą stronę.
Przewidywalne zwijanie
Wybierz jeden tryb zwijania dla całej aplikacji i zapamiętuj wybór ludzi (provider zapisuje ciasteczko sidebar_state; odczytaj je w układzie aplikacji). Nie zwijaj paska bocznego automatycznie przy przechodzeniu między stronami.
Dostępność
| Klawisz | Działanie |
|---|---|
Tab | Przechodzi kolejno przez przyciski i linki paska bocznego. |
EnterSpacja | Aktywuje element z fokusem albo rozwija lub zwija sekcję. |
⌘BCtrlB | Zwija lub rozwija pasek boczny z dowolnego miejsca w aplikacji — chyba że piszesz w polu tekstowym lub edytorze tekstu sformatowanego. |
Esc | Na urządzeniach mobilnych zamyka panel z paskiem bocznym i przywraca fokus na wyzwalacz. |
- Punkty orientacyjne. Owiń menu w
<nav aria-label="…">;SidebarInsetrenderuje<main>strony. - Bieżąca strona. Oprócz stylizacji
isActiveustawiaaria-current="page"na przycisku lub linku elementu. - Pasek ikon. Zwinięte elementy zachowują tekst w DOM, więc czytniki ekranu nadal odczytują etykietę; podpowiedzi pokazują tę samą nazwę osobom widzącym.
- Kontrolki zwijania.
SidebarTriggerma etykietę „Pokaż lub ukryj pasek boczny” — w języku ustawionym przez LocaleProvider.SidebarRailto ułatwienie dla myszy i jest wyłączony z kolejności tabulacji — użytkownicy klawiatury mają do dyspozycji wyzwalacz i skrót. - Urządzenia mobilne. Panel jest modalnym oknem dialogowym: fokus przechodzi do jego wnętrza, nie może go opuścić, dopóki panel jest otwarty, i wraca na wyzwalacz po zamknięciu.
Dokumentacja API
SidebarProvider
Przechowuje stan otwarcia, zapisuje go w ciasteczku i rejestruje skrót klawiszowy. Renderuje <div>, który układa pasek boczny i obszar strony obok siebie.
Sidebar
Panel nawigacji. Na urządzeniach mobilnych renderuje się wewnątrz komponentu Sheet.
SidebarMenuButton
Główna kontrolka elementu menu. Renderuje <button>; do nawigacji przekaż render={<Link />}.
SidebarMenuAction
Akcja pomocnicza na końcu elementu menu.
SidebarMenuSubButton
Link w podmenu. Domyślnie renderuje <a>.
SidebarInset
Obszar strony obok paska bocznego. Renderuje <main>; jeśli strona ma już taki element, przekaż render, aby użyć innego.
SidebarMenuSkeleton
Zastępczy wiersz na czas ładowania. showIcon dodaje przed tekstem blok wielkości ikony.
Pozostałe części
SidebarHeader, SidebarFooter, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuBadge i SidebarSeparator to ostylowane elementy układu, które przyjmują propsy swojego elementu HTML. SidebarGroupLabel i SidebarGroupAction przyjmują render. SidebarInput to kompaktowy Input. SidebarTrigger to ikonowy Button w wariancie ghost; SidebarRail to krawędź reagująca na najechanie, która zwija i rozwija pasek boczny.
useSidebar
Zwraca stan paska bocznego. Poza SidebarProvider rzuca błąd.