Przejdź do treści

Sidebar

Stabilny

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.

  • 12
Ulubione

Instalacja

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/sidebar.json

Uż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.

app/(app)/layout.tsx
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>
  )
}
components/app-sidebar.tsx
"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 header

Przykł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"SidebarRailSidebarTrigger. Kliknij wyzwalacz, kliknij szynę wzdłuż prawej krawędzi paska albo naciśnij B, aby zwinąć go do paska ikon.

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ź.

Strona główna

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.

inset

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.

Dokumentacja
<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.

Poczta
  • 24
  • 3
Projekty

Ł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.

Projekty

Tryby zwijania

collapsiblePo zwinięciu na desktopieWybierz, gdy
offcanvas (domyślnie)Wysuwa się całkowicie poza widok.Treść potrzebuje całej szerokości, a z nawigacji korzysta się sporadycznie.
iconKurczy się do paska ikon o szerokości 3 rem z podpowiedziami.Ludzie bez przerwy przełączają się między sekcjami.
noneNigdy się nie zwija; renderuje się jako zwykły panel.Strony ustawień, osadzona nawigacja, podglądy.

Na ekranach węższych niż 768 px każdy pasek boczny staje się panelem Sheet, który wysuwa się od strony wskazanej w side; otwiera go SidebarTrigger.

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.

TokenDomyślnie (jasny / ciemny)Rola
--sidebargray-1 / gray-1Powierzchnia
--sidebar-foregroundgray-12Tekst
--sidebar-accentgray-4Elementy po najechaniu i aktywne
--sidebar-accent-foregroundgray-12Tekst na akcencie
--sidebar-bordergray-6 / gray-5Obramowania, linia podmenu
--sidebar-ringbrand-9 / brand-10Pierścień fokusu

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.

Przestrzeń roboczaStrona głównaSkrzynka odbiorcza
ProjektyDesign systemPremiera w Q4
Dobrze.Grupy z etykietami, a w nich krótkie, jednolite nazwy miejsc docelowych.
Strona głównaUtwórz nowy projektDesign systemUstawienia powiadomieńSkrzynka odbiorcza
Źle.Jedna długa lista bez etykiet, w której miejsca docelowe mieszają się z akcjami i ustawieniami.

Pokazuj bieżące położenie

Oznacz jako bieżący dokładnie jeden element za pomocą isActive — i ustaw aria-current="page" na jego linku, ż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ść

KlawiszDział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="…">; SidebarInset renderuje <main> strony.
  • Bieżąca strona. isActive jedynie stylizuje element. Dodaj aria-current="page" do aktywnego linku.
  • 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. SidebarTrigger ma etykietę „Pokaż lub ukryj pasek boczny” — w języku ustawionym przez LocaleProvider. SidebarRail to 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.

PropTypDomyślnie
defaultOpen

Czy pasek boczny na desktopie jest na początku rozwinięty (tryb niekontrolowany).

booleantrue
open

Czy pasek boczny na desktopie jest rozwinięty (tryb kontrolowany). Używaj razem z onOpenChange.

booleanBrak wartości domyślnej
onOpenChange

Wywoływana, gdy pasek boczny na desktopie się rozwija lub zwija.

(open: boolean) => voidBrak wartości domyślnej
style

Nadpisuje --sidebar-width i --sidebar-width-icon.

CSSPropertiesBrak wartości domyślnej

Panel nawigacji. Na urządzeniach mobilnych renderuje się wewnątrz komponentu Sheet.

PropTypDomyślnie
side

Krawędź, przy której znajduje się pasek boczny.

"left" | "right""left"
variant

Wygląd na desktopie.

"sidebar" | "floating" | "inset""sidebar"
collapsible

Sposób zwijania paska na desktopie.

"offcanvas" | "icon" | "none""offcanvas"

SidebarMenuButton

Główna kontrolka elementu menu. Renderuje <button>; do nawigacji przekaż render={<Link />}.

PropTypDomyślnie
isActive

Stylizuje element jako bieżący (data-active). Na linkach ustaw też aria-current.

booleanfalse
variant

outline dodaje cienkie obramowanie.

"default" | "outline""default"
size

Wysokość wiersza: 32 px, 28 px lub 48 px.

"default" | "sm" | "lg""default"
tooltip

Etykieta pokazywana po najechaniu, gdy pasek jest zwinięty do ikon.

string | TooltipContent propsBrak wartości domyślnej
render

Renderuje inny element, na przykład Link z Next.js.

ReactElement | (props, state) => ReactElementBrak wartości domyślnej

SidebarMenuAction

Akcja pomocnicza na końcu elementu menu.

PropTypDomyślnie
showOnHover

Ukrywa akcję do czasu najechania na element lub ustawienia na nim fokusu (tylko na desktopie).

booleanfalse
render

Renderuje inny element, np. DropdownMenuTrigger.

ReactElement | (props, state) => ReactElementBrak wartości domyślnej

SidebarMenuSubButton

Link w podmenu. Domyślnie renderuje <a>.

PropTypDomyślnie
isActive

Stylizuje element jako bieżący.

booleanfalse
size

Rozmiar tekstu elementu.

"sm" | "md""md"
render

Renderuje inny element, na przykład Link lub przycisk.

ReactElement | (props, state) => ReactElementBrak wartości domyślnej

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, SidebarMenuBadgeSidebarSeparator to ostylowane elementy układu, które przyjmują propsy swojego elementu HTML. SidebarGroupLabelSidebarGroupAction 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.

PropTypDomyślnie
state

Stan na desktopie, odzwierciedlony w atrybucie data-state paska.

"expanded" | "collapsed"Brak wartości domyślnej
open / setOpen

Stan otwarcia na desktopie.

boolean / (open: boolean) => voidBrak wartości domyślnej
openMobile / setOpenMobile

Stan panelu na urządzeniach mobilnych.

boolean / (open: boolean) => voidBrak wartości domyślnej
isMobile

Ma wartość true poniżej breakpointu md (768 px).

booleanBrak wartości domyślnej
toggleSidebar

Przełącza stan właściwy dla bieżącej szerokości ekranu.

() => voidBrak wartości domyślnej