사이드바

사이드바 (Sidebar)

조립식으로 만들 수 있고, 테마를 입힐 수 있으며, 자유롭게 커스터마이즈할 수 있는 사이드바 컴포넌트예요. 앱에서 가장 많이 쓰이는 네비게이션 영역을 한 번에 깔끔하게 잡아 주는 컴포넌트죠. 다양한 상태(열림/접힘)와 모바일 대응까지 함께 다뤄야 해서, 잘 만들면 앱 전체의 느낌이 확 살아나요.

출처: 문서

본문

아이콘으로 접히는 사이드바.

사이드바는 만들기 가장 까다로운 컴포넌트 중 하나예요. 거의 모든 애플리케이션의 중심에 있고, 움직이는 부분도 많죠.

저는 사이드바 만드는 걸 좋아하지 않아요. 그래서 30개가 넘는 사이드바를 만들었어요. 온갖 방식의 구성으로요. 그 다음에 핵심 컴포넌트만 추려서 sidebar.tsx로 만들었어요.

이제 그 위에 쌓아 올릴 수 있는 탄탄한 기반이 생겼어요. 조립식이고, 테마를 입힐 수 있고, 커스터마이즈할 수 있어요.

블록 라이브러리 구경하기.

설치하기

CLI Manual

sidebar.tsx를 설치하기 위해 다음 명령어를 실행해 주세요.

npx shadcn@latest add sidebar

CSS 파일에 다음 색상을 추가해 주세요.

위 명령어가 색상을 자동으로 설치해 줄 거예요. 만약 설치되지 않았다면, 아래 코드를 CSS 파일에 복사해서 붙여넣어 주세요.

색상에 대한 자세한 내용은 뒤의 테마 섹션에서 다룰게요.

@layer base {
  :root {
    --sidebar-background: 0 0% 98%;
    --sidebar-foreground: 240 5.3% 26.1%;
    --sidebar-primary: 240 5.9% 10%;
    --sidebar-primary-foreground: 0 0% 98%;
    --sidebar-accent: 240 4.8% 95.9%;
    --sidebar-accent-foreground: 240 5.9% 10%;
    --sidebar-border: 220 13% 91%;
    --sidebar-ring: 217.2 91.2% 59.8%;
  }

  .dark {
    --sidebar-background: 240 5.9% 10%;
    --sidebar-foreground: 240 4.8% 95.9%;
    --sidebar-primary: 224.3 76.3% 48%;
    --sidebar-primary-foreground: 0 0% 100%;
    --sidebar-accent: 240 3.7% 15.9%;
    --sidebar-accent-foreground: 240 4.8% 95.9%;
    --sidebar-border: 240 3.7% 15.9%;
    --sidebar-ring: 217.2 91.2% 59.8%;
  }
}

다음 코드를 프로젝트에 복사해서 붙여넣어 주세요.

import 경로를 프로젝트 설정에 맞게 바꿔 주세요.

CSS 파일에 다음 색상을 추가해 주세요.

색상에 대한 자세한 내용은 뒤의 테마 섹션에서 다룰게요.

@layer base {
  :root {
    --sidebar-background: 0 0% 98%;
    --sidebar-foreground: 240 5.3% 26.1%;
    --sidebar-primary: 240 5.9% 10%;
    --sidebar-primary-foreground: 0 0% 98%;
    --sidebar-accent: 240 4.8% 95.9%;
    --sidebar-accent-foreground: 240 5.9% 10%;
    --sidebar-border: 220 13% 91%;
    --sidebar-ring: 217.2 91.2% 59.8%;
  }

  .dark {
    --sidebar-background: 240 5.9% 10%;
    --sidebar-foreground: 240 4.8% 95.9%;
    --sidebar-primary: 224.3 76.3% 48%;
    --sidebar-primary-foreground: 0 0% 100%;
    --sidebar-accent: 240 3.7% 15.9%;
    --sidebar-accent-foreground: 240 4.8% 95.9%;
    --sidebar-border: 240 3.7% 15.9%;
    --sidebar-ring: 217.2 91.2% 59.8%;
  }
}

tailwind.config.js에 사이드바 tailwind 설정을 추가해 주세요.

tailwind.config.js 파일의 theme.extend.colors 섹션에 다음 객체를 추가해 주세요. 이 설정은 bg-sidebar 같은 사이드바 관련 스타일 유틸리티를 활성화해 줘요.

// ...
sidebar: {
  DEFAULT: 'hsl(var(--sidebar-background))',
  foreground: 'hsl(var(--sidebar-foreground))',
  primary: 'hsl(var(--sidebar-primary))',
  'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
  accent: 'hsl(var(--sidebar-accent))',
  'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
  border: 'hsl(var(--sidebar-border))',
  ring: 'hsl(var(--sidebar-ring))',
},
// ...

구조

Sidebar 컴포넌트는 다음 부분들로 구성돼요.

  • SidebarProvider - 접힘(collapsible) 상태를 관리해요.
  • Sidebar - 사이드바 컨테이너예요.
  • SidebarHeader와 SidebarFooter - 사이드바의 상단과 하단에 고정돼요.
  • SidebarContent - 스크롤 가능한 콘텐츠 영역이에요.
  • SidebarGroup - SidebarContent 안의 섹션이에요.
  • SidebarTrigger - Sidebar를 여닫는 트리거예요.

Sidebar Structure Sidebar Structure

사용하기

레이아웃의 루트를 SidebarProvider로 감싸고 사이드바를 배치해요.

import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
import { AppSidebar } from "@/components/app-sidebar"

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <SidebarProvider>
      <AppSidebar />
      <main>
        <SidebarTrigger />
        {children}
      </main>
    </SidebarProvider>
  )
}
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarHeader,
} from "@/components/ui/sidebar"

export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarHeader />
      <SidebarContent>
        <SidebarGroup />
        <SidebarGroup />
      </SidebarContent>
      <SidebarFooter />
    </Sidebar>
  )
}

첫 번째 사이드바 만들기

가장 기본적인 사이드바부터 시작해 볼게요. 메뉴가 있는 접히는 사이드바예요.

애플리케이션의 루트에 `SidebarProvider`와 `SidebarTrigger`를 추가해 주세요.
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
import { AppSidebar } from "@/components/app-sidebar"

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <SidebarProvider>
      <AppSidebar />
      <main>
        <SidebarTrigger />
        {children}
      </main>
    </SidebarProvider>
  )
}

components/app-sidebar.tsx에 새 사이드바 컴포넌트를 만들어 주세요.

import { Sidebar, SidebarContent } from "@/components/ui/sidebar"

export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent />
    </Sidebar>
  )
}

이제 사이드바에 SidebarMenu를 추가해 볼게요.

SidebarGroup 안에서 SidebarMenu 컴포넌트를 사용할 거예요.

import { Calendar, Home, Inbox, Search, Settings } from "lucide-react"

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from "@/components/ui/sidebar"

// Menu items.
const items = [
  {
    title: "Home",
    url: "#",
    icon: Home,
  },
  {
    title: "Inbox",
    url: "#",
    icon: Inbox,
  },
  {
    title: "Calendar",
    url: "#",
    icon: Calendar,
  },
  {
    title: "Search",
    url: "#",
    icon: Search,
  },
  {
    title: "Settings",
    url: "#",
    icon: Settings,
  },
]

export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Application</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              {items.map((item) => (
                <SidebarMenuItem key={item.title}>
                  <SidebarMenuButton asChild>
                    <a href={item.url}>
                      <item.icon />
                      <span>{item.title}</span>
                    </a>
                  </SidebarMenuButton>
                </SidebarMenuItem>
              ))}
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
  )
}

첫 번째 사이드바를 만들었어요.

첫 번째 사이드바.

컴포넌트

sidebar.tsx의 컴포넌트들은 조립식으로 설계됐어요. 즉 제공된 컴포넌트들을 조합해서 여러분만의 사이드바를 만드는 방식이에요. DropdownMenu, Collapsible, Dialog 같은 다른 shadcn/ui 컴포넌트들과도 잘 어울려요.

sidebar.tsx의 코드를 바꿔야 한다면 얼마든지 바꾸세요. 이 코드는 여러분의 코드예요. sidebar.tsx를 출발점으로 삼아 여러분만의 사이드바를 만들어 보세요.

다음 섹션들에서 각 컴포넌트와 사용법을 하나씩 살펴볼게요.

SidebarProvider

SidebarProvider 컴포넌트는 Sidebar 컴포넌트에 사이드바 컨텍스트를 제공하는 역할을 해요. 애플리케이션을 항상 SidebarProvider 컴포넌트로 감싸 주어야 해요.

Props

Name Type Description
defaultOpen boolean 사이드바의 기본 열림 상태.
open boolean 사이드바의 열림 상태 (제어).
onOpenChange (open: boolean) => void 사이드바의 열림 상태를 설정 (제어).

너비

애플리케이션에 사이드바가 하나뿐이라면, sidebar.tsx에 있는 SIDEBAR_WIDTH와 SIDEBAR_WIDTH_MOBILE 변수를 사용해서 사이드바의 너비를 정할 수 있어요.

const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"

애플리케이션에 사이드바가 여러 개라면, style prop을 사용해서 사이드바의 너비를 정할 수 있어요.

사이드바의 너비를 설정하려면, style prop에 --sidebar-width와 --sidebar-width-mobile CSS 변수를 넣으면 돼요.

<SidebarProvider
  style={{
    "--sidebar-width": "20rem",
    "--sidebar-width-mobile": "20rem",
  }}
>
  <Sidebar />
</SidebarProvider>

이렇게 하면 사이드바의 너비는 물론 레이아웃의 간격까지 함께 처리돼요.

키보드 단축키

SIDEBAR_KEYBOARD_SHORTCUT 변수는 사이드바를 열고 닫는 데 사용할 키보드 단축키를 설정해요.

사이드바를 토글하려면 Mac에서는 cmd+b, Windows에서는 ctrl+b 키를 사용해요.

SIDEBAR_KEYBOARD_SHORTCUT 변수를 수정하면 키보드 단축키를 바꿀 수 있어요.

const SIDEBAR_KEYBOARD_SHORTCUT = "b"

상태 유지

SidebarProvider는 페이지를 다시 불러오거나 서버 사이드 렌더링을 거쳐도 사이드바 상태를 유지하는 것을 지원해요. 쿠키를 사용해서 사이드바의 현재 상태를 저장하죠. 사이드바 상태가 바뀌면 sidebar:state라는 기본 쿠키에 현재 열림/닫힘 상태가 저장돼요. 이 쿠키는 이후 페이지를 불러올 때 읽어서 사이드바 상태를 복원해요.

Next.js에서 사이드바 상태를 유지하려면 app/layout.tsx에 SidebarProvider를 이렇게 구성해 주세요.

import { cookies } from "next/headers"

import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
import { AppSidebar } from "@/components/app-sidebar"

export async function Layout({ children }: { children: React.ReactNode }) {
  const cookieStore = await cookies()
  const defaultOpen = cookieStore.get("sidebar:state")?.value === "true"

  return (
    <SidebarProvider defaultOpen={defaultOpen}>
      <AppSidebar />
      <main>
        <SidebarTrigger />
        {children}
      </main>
    </SidebarProvider>
  )
}

sidebar.tsx의 SIDEBAR_COOKIE_NAME 변수를 수정하면 쿠키의 이름을 바꿀 수 있어요.

const SIDEBAR_COOKIE_NAME = "sidebar:state"

접히는 사이드바를 렌더링하는 메인 Sidebar 컴포넌트예요.

import { Sidebar } from "@/components/ui/sidebar"

export function AppSidebar() {
  return <Sidebar />
}

Props

Property Type Description
side left 또는 right 사이드바의 위치.
variant sidebar, floating, inset 사이드바의 변형.
collapsible offcanvas, icon, none 사이드바의 접힘 상태.

side

side prop을 사용해서 사이드바의 위치를 바꿀 수 있어요.

사용 가능한 옵션은 left와 right예요.

import { Sidebar } from "@/components/ui/sidebar"

export function AppSidebar() {
  return <Sidebar side="left | right" />
}

variant

variant prop을 사용해서 사이드바의 변형을 바꿀 수 있어요.

사용 가능한 옵션은 sidebar, floating, inset이에요.

import { Sidebar } from "@/components/ui/sidebar"

export function AppSidebar() {
  return <Sidebar variant="sidebar | floating | inset" />
}
**참고:** `inset` 변형을 사용한다면 메인 콘텐츠를 `SidebarInset` 컴포넌트로 감싸 주는 것을 잊지 마세요.
<SidebarProvider>
  <Sidebar variant="inset" />
  <SidebarInset>
    <main>{children}</main>
  </SidebarInset>
</SidebarProvider>

collapsible

collapsible prop을 사용해서 사이드바를 접힐 수 있게 만들 수 있어요.

사용 가능한 옵션은 offcanvas, icon, none이에요.

import { Sidebar } from "@/components/ui/sidebar"

export function AppSidebar() {
  return <Sidebar collapsible="offcanvas | icon | none" />
}
Prop Description
offcanvas 왼쪽이나 오른쪽에서 슬라이드로 들어오는 접히는 사이드바.
icon 아이콘으로 접히는 사이드바.
none 접히지 않는 사이드바.

useSidebar

useSidebar 훅은 사이드바를 제어할 때 사용해요.

import { useSidebar } from "@/components/ui/sidebar"

export function AppSidebar() {
  const {
    state,
    open,
    setOpen,
    openMobile,
    setOpenMobile,
    isMobile,
    toggleSidebar,
  } = useSidebar()
}
Property Type Description
state expanded 또는 collapsed 사이드바의 현재 상태.
open boolean 사이드바가 열려 있는지 여부.
setOpen (open: boolean) => void 사이드바의 열림 상태를 설정.
openMobile boolean 모바일에서 사이드바가 열려 있는지 여부.
setOpenMobile (open: boolean) => void 모바일에서 사이드바의 열림 상태를 설정.
isMobile boolean 모바일인지 여부.
toggleSidebar () => void 사이드바를 토글. 데스크톱과 모바일 모두.

SidebarHeader

SidebarHeader 컴포넌트는 사이드바에 고정된 헤더를 추가할 때 사용해요.

다음 예시는 SidebarHeader에 <DropdownMenu>를 추가한 모습이에요.

드롭다운 메뉴를 가진 사이드바 헤더.
<Sidebar>
  <SidebarHeader>
    <SidebarMenu>
      <SidebarMenuItem>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <SidebarMenuButton>
              Select Workspace
              <ChevronDown className="ml-auto" />
            </SidebarMenuButton>
          </DropdownMenuTrigger>
          <DropdownMenuContent className="w-[--radix-popper-anchor-width]">
            <DropdownMenuItem>
              <span>Acme Inc</span>
            </DropdownMenuItem>
            <DropdownMenuItem>
              <span>Acme Corp.</span>
            </DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      </SidebarMenuItem>
    </SidebarMenu>
  </SidebarHeader>
</Sidebar>

SidebarFooter

SidebarFooter 컴포넌트는 사이드바에 고정된 푸터를 추가할 때 사용해요.

다음 예시는 SidebarFooter에 <DropdownMenu>를 추가한 모습이에요.

드롭다운 메뉴를 가진 사이드바 푸터.
export function AppSidebar() {
  return (
    <SidebarProvider>
      <Sidebar>
        <SidebarHeader />
        <SidebarContent />
        <SidebarFooter>
          <SidebarMenu>
            <SidebarMenuItem>
              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <SidebarMenuButton>
                    <User2 /> Username
                    <ChevronUp className="ml-auto" />
                  </SidebarMenuButton>
                </DropdownMenuTrigger>
                <DropdownMenuContent
                  side="top"
                  className="w-[--radix-popper-anchor-width]"
                >
                  <DropdownMenuItem>
                    <span>Account</span>
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <span>Billing</span>
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <span>Sign out</span>
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarFooter>
      </Sidebar>
    </SidebarProvider>
  )
}

SidebarContent

SidebarContent 컴포넌트는 사이드바의 콘텐츠를 감싸는 역할을 해요. 여기에 SidebarGroup 컴포넌트를 추가하면 돼요. 스크롤이 가능해요.

import { Sidebar, SidebarContent } from "@/components/ui/sidebar"

export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup />
        <SidebarGroup />
      </SidebarContent>
    </Sidebar>
  )
}

SidebarGroup

SidebarGroup 컴포넌트는 사이드바 안에 섹션을 만들 때 사용해요.

SidebarGroup은 SidebarGroupLabel, SidebarGroupContent, 그리고 선택적으로 SidebarGroupAction을 가져요.

사이드바 그룹.
import { Sidebar, SidebarContent, SidebarGroup } from "@/components/ui/sidebar"

export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Application</SidebarGroupLabel>
          <SidebarGroupAction>
            <Plus /> <span className="sr-only">Add Project</span>
          </SidebarGroupAction>
          <SidebarGroupContent></SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
  )
}

접히는 SidebarGroup

SidebarGroup을 접힐 수 있게 하려면, Collapsible로 감싸면 돼요.

접히는 사이드바 그룹.
export function AppSidebar() {
  return (
    <Collapsible defaultOpen className="group/collapsible">
      <SidebarGroup>
        <SidebarGroupLabel asChild>
          <CollapsibleTrigger>
            Help
            <ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
          </CollapsibleTrigger>
        </SidebarGroupLabel>
        <CollapsibleContent>
          <SidebarGroupContent />
        </CollapsibleContent>
      </SidebarGroup>
    </Collapsible>
  )
}
**참고:** 버튼을 렌더링하기 위해 `CollapsibleTrigger`를 `SidebarGroupLabel`로 감싸요.

SidebarGroupAction

SidebarGroupAction 컴포넌트는 SidebarGroup에 액션 버튼을 추가할 때 사용해요.

export function AppSidebar() {
  return (
    <SidebarGroup>
      <SidebarGroupLabel asChild>Projects</SidebarGroupLabel>
      <SidebarGroupAction title="Add Project">
        <Plus /> <span className="sr-only">Add Project</span>
      </SidebarGroupAction>
      <SidebarGroupContent />
    </SidebarGroup>
  )
}
액션 버튼이 있는 사이드바 그룹.

SidebarMenu

SidebarMenu 컴포넌트는 SidebarGroup 안에 메뉴를 만들 때 사용해요.

SidebarMenu 컴포넌트는 SidebarMenuItem, SidebarMenuButton, <SidebarMenuAction />, <SidebarMenuSub /> 컴포넌트로 구성돼요.

Sidebar Menu Sidebar Menu

SidebarMenu 컴포넌트가 프로젝트 목록을 렌더링하는 예시예요.

프로젝트 목록이 있는 사이드바 메뉴.
<Sidebar>
  <SidebarContent>
    <SidebarGroup>
      <SidebarGroupLabel>Projects</SidebarGroupLabel>
      <SidebarGroupContent>
        <SidebarMenu>
          {projects.map((project) => (
            <SidebarMenuItem key={project.name}>
              <SidebarMenuButton asChild>
                <a href={project.url}>
                  <project.icon />
                  <span>{project.name}</span>
                </a>
              </SidebarMenuButton>
            </SidebarMenuItem>
          ))}
        </SidebarMenu>
      </SidebarGroupContent>
    </SidebarGroup>
  </SidebarContent>
</Sidebar>

SidebarMenuButton

SidebarMenuButton 컴포넌트는 SidebarMenuItem 안에 메뉴 버튼을 렌더링할 때 사용해요.

링크 또는 앵커

기본적으로 SidebarMenuButton은 버튼을 렌더링하지만, asChild prop을 사용하면 Link나 a 태그처럼 다른 컴포넌트를 렌더링할 수 있어요.

<SidebarMenuButton asChild>
  <a href="#">Home</a>
</SidebarMenuButton>

아이콘과 라벨

버튼 안에 아이콘과 잘린 라벨을 렌더링할 수 있어요. 라벨은 <span>으로 감싸 주는 것을 잊지 마세요.

<SidebarMenuButton asChild>
  <a href="#">
    <Home />
    <span>Home</span>
  </a>
</SidebarMenuButton>

isActive

isActive prop을 사용해서 메뉴 항목을 활성 상태로 표시할 수 있어요.

<SidebarMenuButton asChild isActive>
  <a href="#">Home</a>
</SidebarMenuButton>

SidebarMenuAction

SidebarMenuAction 컴포넌트는 SidebarMenuItem 안에 메뉴 액션을 렌더링할 때 사용해요.

이 버튼은 SidebarMenuButton과 독립적으로 동작해요. 즉 <SidebarMenuButton />을 클릭 가능한 링크로 두고, <SidebarMenuAction />을 버튼으로 쓸 수 있죠.

<SidebarMenuItem>
  <SidebarMenuButton asChild>
    <a href="#">
      <Home />
      <span>Home</span>
    </a>
  </SidebarMenuButton>
  <SidebarMenuAction>
    <Plus /> <span className="sr-only">Add Project</span>
  </SidebarMenuAction>
</SidebarMenuItem>

DropdownMenu

SidebarMenuAction 컴포넌트가 DropdownMenu를 렌더링하는 예시예요.

드롭다운 메뉴가 있는 사이드바 메뉴 액션.
<SidebarMenuItem>
  <SidebarMenuButton asChild>
    <a href="#">
      <Home />
      <span>Home</span>
    </a>
  </SidebarMenuButton>
  <DropdownMenu>
    <DropdownMenuTrigger asChild>
      <SidebarMenuAction>
        <MoreHorizontal />
      </SidebarMenuAction>
    </DropdownMenuTrigger>
    <DropdownMenuContent side="right" align="start">
      <DropdownMenuItem>
        <span>Edit Project</span>
      </DropdownMenuItem>
      <DropdownMenuItem>
        <span>Delete Project</span>
      </DropdownMenuItem>
    </DropdownMenuContent>
  </DropdownMenu>
</SidebarMenuItem>

SidebarMenuSub

SidebarMenuSub 컴포넌트는 SidebarMenu 안에 하위 메뉴를 렌더링할 때 사용해요.

하위 메뉴 항목을 렌더링하려면 <SidebarMenuSubItem />과 <SidebarMenuSubButton />을 사용해요.

하위 메뉴가 있는 사이드바 메뉴.
<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuSub>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton />
    </SidebarMenuSubItem>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton />
    </SidebarMenuSubItem>
  </SidebarMenuSub>
</SidebarMenuItem>

접히는 SidebarMenu

SidebarMenu 컴포넌트를 접힐 수 있게 하려면, 그 컴포넌트와 SidebarMenuSub 컴포넌트를 Collapsible로 감싸면 돼요.

접히는 사이드바 메뉴.
<SidebarMenu>
  <Collapsible defaultOpen className="group/collapsible">
    <SidebarMenuItem>
      <CollapsibleTrigger asChild>
        <SidebarMenuButton />
      </CollapsibleTrigger>
      <CollapsibleContent>
        <SidebarMenuSub>
          <SidebarMenuSubItem />
        </SidebarMenuSub>
      </CollapsibleContent>
    </SidebarMenuItem>
  </Collapsible>
</SidebarMenu>

SidebarMenuBadge

SidebarMenuBadge 컴포넌트는 SidebarMenuItem 안에 배지를 렌더링할 때 사용해요.

배지가 있는 사이드바 메뉴.
<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuBadge>24</SidebarMenuBadge>
</SidebarMenuItem>

SidebarMenuSkeleton

SidebarMenuSkeleton 컴포넌트는 SidebarMenu의 스켈레톤을 렌더링할 때 사용해요. React Server Components, SWR, react-query를 사용할 때 로딩 상태를 보여주는 데 쓸 수 있어요.

function NavProjectsSkeleton() {
  return (
    <SidebarMenu>
      {Array.from({ length: 5 }).map((_, index) => (
        <SidebarMenuItem key={index}>
          <SidebarMenuSkeleton />
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}

SidebarSeparator

SidebarSeparator 컴포넌트는 Sidebar 안에 구분선을 렌더링할 때 사용해요.

<Sidebar>
  <SidebarHeader />
  <SidebarSeparator />
  <SidebarContent>
    <SidebarGroup />
    <SidebarSeparator />
    <SidebarGroup />
  </SidebarContent>
</Sidebar>

SidebarTrigger

SidebarTrigger 컴포넌트는 사이드바를 토글하는 버튼을 렌더링할 때 사용해요.

SidebarTrigger 컴포넌트는 반드시 SidebarProvider 안에서 사용해야 해요.

<SidebarProvider>
  <Sidebar />
  <main>
    <SidebarTrigger />
  </main>
</SidebarProvider>

커스텀 트리거

커스텀 트리거를 만들려면 useSidebar 훅을 사용할 수 있어요.

import { useSidebar } from "@/components/ui/sidebar"

export function CustomTrigger() {
  const { toggleSidebar } = useSidebar()

  return <button onClick={toggleSidebar}>Toggle Sidebar</button>
}

SidebarRail

SidebarRail 컴포넌트는 Sidebar 안에 레일을 렌더링할 때 사용해요. 이 레일로 사이드바를 토글할 수 있어요.

<Sidebar>
  <SidebarHeader />
  <SidebarContent>
    <SidebarGroup />
  </SidebarContent>
  <SidebarFooter />
  <SidebarRail />
</Sidebar>

데이터 가져오기

React Server Components

SidebarMenu 컴포넌트가 React Server Components를 사용해서 프로젝트 목록을 렌더링하는 예시예요.

React Server Components를 사용하는 사이드바 메뉴.
function NavProjectsSkeleton() {
  return (
    <SidebarMenu>
      {Array.from({ length: 5 }).map((_, index) => (
        <SidebarMenuItem key={index}>
          <SidebarMenuSkeleton showIcon />
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}
async function NavProjects() {
  const projects = await fetchProjects()

  return (
    <SidebarMenu>
      {projects.map((project) => (
        <SidebarMenuItem key={project.name}>
          <SidebarMenuButton asChild>
            <a href={project.url}>
              <project.icon />
              <span>{project.name}</span>
            </a>
          </SidebarMenuButton>
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}
function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Projects</SidebarGroupLabel>
          <SidebarGroupContent>
            <React.Suspense fallback={<NavProjectsSkeleton />}>
              <NavProjects />
            </React.Suspense>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
  )
}

SWR과 React Query

SWR이나 react-query를 사용해서도 같은 방식으로 적용할 수 있어요.

function NavProjects() {
  const { data, isLoading } = useSWR("/api/projects", fetcher)

  if (isLoading) {
    return (
      <SidebarMenu>
        {Array.from({ length: 5 }).map((_, index) => (
          <SidebarMenuItem key={index}>
            <SidebarMenuSkeleton showIcon />
          </SidebarMenuItem>
        ))}
      </SidebarMenu>
    )
  }

  if (!data) {
    return ...
  }

  return (
    <SidebarMenu>
      {data.map((project) => (
        <SidebarMenuItem key={project.name}>
          <SidebarMenuButton asChild>
            <a href={project.url}>
              <project.icon />
              <span>{project.name}</span>
            </a>
          </SidebarMenuButton>
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}
function NavProjects() {
  const { data, isLoading } = useQuery()

  if (isLoading) {
    return (
      <SidebarMenu>
        {Array.from({ length: 5 }).map((_, index) => (
          <SidebarMenuItem key={index}>
            <SidebarMenuSkeleton showIcon />
          </SidebarMenuItem>
        ))}
      </SidebarMenu>
    )
  }

  if (!data) {
    return ...
  }

  return (
    <SidebarMenu>
      {data.map((project) => (
        <SidebarMenuItem key={project.name}>
          <SidebarMenuButton asChild>
            <a href={project.url}>
              <project.icon />
              <span>{project.name}</span>
            </a>
          </SidebarMenuButton>
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}

제어되는 사이드바 (Controlled Sidebar)

open과 onOpenChange prop을 사용해서 사이드바를 제어할 수 있어요.

제어되는 사이드바.
export function AppSidebar() {
  const [open, setOpen] = React.useState(false)

  return (
    <SidebarProvider open={open} onOpenChange={setOpen}>
      <Sidebar />
    </SidebarProvider>
  )
}

테마 (Theming)

사이드바를 테마로 꾸미기 위해 다음 CSS 변수를 사용해요.

@layer base {
  :root {
    --sidebar-background: 0 0% 98%;
    --sidebar-foreground: 240 5.3% 26.1%;
    --sidebar-primary: 240 5.9% 10%;
    --sidebar-primary-foreground: 0 0% 98%;
    --sidebar-accent: 240 4.8% 95.9%;
    --sidebar-accent-foreground: 240 5.9% 10%;
    --sidebar-border: 220 13% 91%;
    --sidebar-ring: 217.2 91.2% 59.8%;
  }

  .dark {
    --sidebar-background: 240 5.9% 10%;
    --sidebar-foreground: 240 4.8% 95.9%;
    --sidebar-primary: 0 0% 98%;
    --sidebar-primary-foreground: 240 5.9% 10%;
    --sidebar-accent: 240 3.7% 15.9%;
    --sidebar-accent-foreground: 240 4.8% 95.9%;
    --sidebar-border: 240 3.7% 15.9%;
    --sidebar-ring: 217.2 91.2% 59.8%;
  }
}

사이드바와 애플리케이션 나머지 부분에 서로 다른 변수를 의도적으로 사용해요. 이렇게 하면 애플리케이션의 나머지 부분과 다르게 스타일을 입힌 사이드바를 쉽게 만들 수 있죠. 메인 애플리케이션보다 더 어두운 색조의 사이드바를 생각해 보세요.

스타일링

다양한 상태에 따라 사이드바를 스타일링하는 몇 가지 팁을 알려드릴게요.

  • 사이드바 접힘 상태에 따라 요소를 스타일링하기. 다음은 사이드바가 icon 모드일 때 SidebarGroup을 숨기는 예시예요.
<Sidebar collapsible="icon">
  <SidebarContent>
    <SidebarGroup className="group-data-[collapsible=icon]:hidden" />
  </SidebarContent>
</Sidebar>
  • 메뉴 버튼 활성 상태에 따라 메뉴 액션을 스타일링하기. 다음은 메뉴 버튼이 활성화됐을 때 메뉴 액션을 강제로 보이게 하는 예시예요.
<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuAction className="peer-data-[active=true]/menu-button:opacity-100" />
</SidebarMenuItem>

상태를 활용한 스타일링에 대한 더 많은 팁은 이 트위터 스레드에서 찾아볼 수 있어요.

변경 내역 (Changelog)

2024-10-30 setOpen의 쿠키 처리

  • #5593 - <SidebarProvider>의 setOpen 콜백 로직 개선.

<SidebarProvider>의 setOpen 콜백을 다음과 같이 업데이트해 주세요.

const setOpen = React.useCallback(
  (value: boolean | ((value: boolean) => boolean)) => {
    const openState = typeof value === "function" ? value(open) : value
    if (setOpenProp) {
      setOpenProp(openState)
    } else {
      _setOpen(openState)
    }

    // This sets the cookie to keep the sidebar state.
    document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
  },
  [setOpenProp, open]
)

2024-10-21 text-sidebar-foreground 수정

  • #5491 - text-sidebar-foreground를 <SidebarProvider>에서 <Sidebar> 컴포넌트로 이동.

2024-10-20 useSidebar 훅의 오타 수정

useSidebar 훅에서 오타를 수정했어요.

-  throw new Error("useSidebar must be used within a Sidebar.")
+  throw new Error("useSidebar must be used within a SidebarProvider.")

더 알아보기 (Learn more)