HextaUI

Sidebar

アイコンのみ、またはオフキャンバスに折りたたまれ、ヘッダーの下に固定され、スマートフォンではスワイプできるシートになるアプリのサイドバーです。

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

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

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

レイアウトを <SidebarProvider /> で囲み、ページはサイドバーの後に <SidebarInset /> の中へ置きます。

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 は、開閉の状態、キーボードショートカット、幅を保持します。
  • Sidebar は、ページがスクロールしても固定されたままの列です。768px未満では sheet になります。
  • SidebarHeader と SidebarFooter は固定されたままです。SidebarContent はそれらの間でスクロールします。
  • SidebarGroup は、ラベルとアクションを任意で持てるセクションです。SidebarMenu はリンクを保持します。
  • SidebarInset は、サイドバーの隣にあるページです。

バリアント

variant は見た目を設定します。全高の sidebar、floating パネル、またはページがサイドバーの色の上でカードになる inset です。切り替えると、レイアウトがアニメーションするのを確認できます。

"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 はサイドバーを画面外へスライドさせ、icon はアイコンに縮めて各ラベルを tooltip に表示し、none は開いたままにします。⌘B または Ctrl+B で、作業中のサイドバーを切り替えます。

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

折りたたみ可能なグループとサブメニュー

SidebarGroup または SidebarMenuItem を Collapsible で囲み、ラベルまたはボタンをトリガーとして描画し、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>
  )
}

右側

side="right" を設定し、SidebarInset の後にサイドバーを置きます。レールとモバイルの sheet もその側に従います。

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

ヘッダーの下

サイドバーは sticky なので、上にあるものの下から始まります。sticky なヘッダーがある場合は、--sidebar-top にその高さを設定すると、サイドバーはその下に固定され、残りの画面に収まります。

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

制御

open と onOpenChange を渡します。トリガー、レール、ショートカットはすべて 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>
  )
}

読み込み中

SidebarMenuSkeleton は、読み込み中のメニューを埋めます。幅は行ごとに異なり、サーバーとクライアントで一致します。

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

右から左

dir="rtl" と side="right" を設定します。余白、サブメニューの線、tooltip、トリガーのアイコンが反転します。

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

サイドバーの幅は16rem、スマートフォンでは18rem、アイコンに折りたたむと3remです。プロバイダーで --sidebar-width、--sidebar-width-mobile、--sidebar-width-icon を上書きします。

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

サイドバーには副作用がありません。開いていたかどうかを記憶するには、onOpenChange で保存し、サーバーで描画するときに defaultOpen へ読み戻します。

"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>
  )
}
キーアクション
⌘ + BCtrl + Bサイドバーを切り替えます。ページに複数のサイドバーがある場合は、フォーカスを持つものが応答し、なければ最初のものが応答します。リッチテキストエディターで入力中は無視されます。
TabShift + Tabリンク間を移動します。キャンバス外に折りたたまれたサイドバーはスキップされます。
EnterSpaceフォーカスされているリンク、ボタン、トリガーを実行します。
Escスマートフォンでサイドバーを閉じます。
  • SidebarTrigger には「Toggle Sidebar」というラベルが付き、aria-expanded と aria-controls を公開します。
  • isActive は aria-current="page" を設定します。
  • キャンバス外へ折りたたむと、コンテンツはキーボードとスクリーンリーダーから隠されます。フォーカスが内側にあった場合は、トリガーに移ります。
  • アイコンに折りたたまれていても、ラベルは各リンクのアクセシブルな名前として残り、ホバーとキーボードフォーカス時に tooltip として表示されます。グループのラベル、アクション、バッジは非表示になります。
  • スマートフォンでは、サイドバーはモーダルの sheet になります。フォーカスはトラップされ、スワイプまたは Esc で閉じ、リンクをたどっても閉じます。新しいタブで開くリンク、ダウンロード、修飾キー付きのクリックでは開いたままです。
  • 視差効果の軽減が有効な場合、折りたたみは即座に行われます。
プロパティ型デフォルト
defaultOpen
booleantrue
open
boolean–
onOpenChange
(open: boolean) => void–
keyboardShortcutmod は Apple のプラットフォームでは ⌘、それ以外では Ctrl です。null でオフになります。
string | null"mod+b"
属性説明
data-slot="sidebar-wrapper"レイアウトのラッパー。
--sidebar-width展開時の幅。デフォルトは16remです。
--sidebar-width-mobileスマートフォン用 sheet の幅。デフォルトは18remです。
--sidebar-width-iconアイコンに折りたたんだときの幅。デフォルトは3remです。
--sidebar-topsticky なヘッダーの下など、スクロール中にサイドバーが固定される位置。デフォルトは0pxです。

className などの props はサイドバーのコンテナーに渡されます。

プロパティ型デフォルト
side
"left" | "right""left"
variantplain は、面、端の線、スクロールバー、メニュー項目間の gap を取り除き、ページ上に置くサイドバー向けにします。
"sidebar" | "floating" | "inset" | "plain""sidebar"
collapsible
"offcanvas" | "icon" | "none""offcanvas"
mobileスマートフォンでの開き方。側面からの sheet か、チャットアプリのように画面全体を埋めるスライドインです。
"sheet" | "fullscreen""sheet"
dirスマートフォン用 sheet の方向も設定します。
"ltr" | "rtl"–
属性説明
data-slot="sidebar"サイドバー、またはスマートフォンでは sheet。
data-state"expanded" または "collapsed"。
data-collapsible折りたたまれている間は collapsible のモード、それ以外は ""。子要素には group-data-[collapsible=icon]: でスタイルを付けます。
data-variantバリアント。
data-side側。
data-mobileスマートフォン用 sheet に付きます。
data-slot="sidebar-container"スライドしてリサイズするパネル。
data-slot="sidebar-inner"コンテンツを保持する面。

toggleSidebar を呼ぶ、ゴースト型のアイコン Button。止めるには onClick で event.preventDefault() を呼びます。アイコンを置き換えるには子要素を渡します。

属性説明
data-slot="sidebar-trigger"トリガー。
aria-expandedサイドバーが開いているかどうか。

サイドバーの端にある細いヒット領域で、クリックで切り替えます。キーボードユーザーにはトリガーとショートカットがあるため、タブ順からは外されています。サイドバーがキャンバス外にあるときも、レールは画面の端に残ります。

属性説明
data-slot="sidebar-rail"レール。

残りの幅を占める <main>。inset サイドバーの隣では、角丸のカードになります。

プロパティ型デフォルト
render別の要素を描画します。ページにすでに <main> ランドマークがある場合は <div /> を渡します。
React.ReactElement | (props) => React.ReactElement–
属性説明
data-slot="sidebar-inset"ページ領域。

プレーンな <div> 要素。コンテンツは細いスクロールバーと柔らかい端のフェードでスクロールします。

属性説明
data-slot="sidebar-header"上部のセクション。
data-slot="sidebar-content"スクロールする中央部。
data-slot="sidebar-footer"下部のセクション。
属性説明
data-slot="sidebar-group"サイドバーのセクション。
data-slot="sidebar-group-content"グループのコンテンツ。
プロパティ型デフォルト
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="sidebar-group-label"アイコンに折りたたまれると、上にスライドしてフェードします。
プロパティ型デフォルト
render
ReactElement | (props, state) => ReactElement<button>
属性説明
data-slot="sidebar-group-action"アクセシブルなラベルが必要です。

<ul> とその <li> 項目。チャット履歴のように行を詰めて並べる密なリストには、gap="none" を設定します。

プロパティ型デフォルト
gap項目間の間隔。SidebarMenuSub にもあります。
"default" | "none""default"
属性説明
data-slot="sidebar-menu"リスト。
data-slot="sidebar-menu-item"項目。ホバー用スタイルには group/menu-item。
プロパティ型デフォルト
isActiveハイライトして aria-current="page" を設定します。
booleanfalse
variant
"default" | "outline""default"
size
"default" | "sm" | "lg""default"
tooltipアイコンに折りたたまれている間表示されます。メニューに沿って動くと、項目間でコンテンツがスライドします。
ReactNode | TooltipContentProps–
render
ReactElement | (props, state) => ReactElement<button>
属性説明
data-slot="sidebar-menu-button"兄弟要素向けのスタイルには peer/menu-button。
data-activeisActive の間付きます。
data-sizeサイズ。
プロパティ型デフォルト
showOnHover項目がホバーまたはフォーカスされている間、またはそのメニューが開いている間だけ表示します。タッチスクリーンでは常に表示されます。
booleanfalse
render
ReactElement | (props, state) => ReactElement<button>
属性説明
data-slot="sidebar-menu-action"アクセシブルなラベルが必要です。
属性説明
data-slot="sidebar-menu-badge"項目の末尾のカウント。
プロパティ型デフォルト
showIcon
booleanfalse
属性説明
data-slot="sidebar-menu-skeleton"支援技術から隠されます。

先頭側の端に線があるネストされたリスト。サイドバーがアイコンに折りたたまれると畳まれます。

プロパティ型デフォルト
isActiveSidebarMenuSubButton 上で。
booleanfalse
sizeSidebarMenuSubButton 上で。
"sm" | "md""md"
renderSidebarMenuSubButton 上で。
ReactElement | (props, state) => ReactElement<a>
属性説明
data-slot="sidebar-menu-sub"ネストされたリスト。
data-slot="sidebar-menu-sub-button"ネストされたリンク。
data-activeisActive の間付きます。

ページ背景上の小さな Input と、細い区切り線。

属性説明
data-slot="sidebar-input"入力欄。
data-slot="sidebar-separator"セパレーター。

最も近いサイドバーを読み取り、制御します。SidebarProvider の外ではエラーを投げます。

const {
  state,
  open,
  setOpen,
  openMobile,
  setOpenMobile,
  isMobile,
  toggleSidebar,
} = useSidebar()
プロパティ型デフォルト
state
"expanded" | "collapsed"–
open
boolean–
setOpen
(open: boolean) => void–
openMobile
boolean–
setOpenMobile
(open: boolean) => void–
isMobile
boolean–
toggleSidebarスマートフォンでは sheet を、それ以外ではサイドバーを切り替えます。
() => void–

使用しているブロック

Sidebar の上に構築されるブロック。