사이드바
사이드바 (Sidebar)
조립식으로 만들 수 있고, 테마를 입힐 수 있으며, 자유롭게 커스터마이즈할 수 있는 사이드바 컴포넌트예요. 앱에서 가장 많이 쓰이는 네비게이션 영역을 한 번에 깔끔하게 잡아 주는 컴포넌트죠. 다양한 상태(열림/접힘)와 모바일 대응까지 함께 다뤄야 해서, 잘 만들면 앱 전체의 느낌이 확 살아나요.
출처: 문서
본문
사이드바는 만들기 가장 까다로운 컴포넌트 중 하나예요. 거의 모든 애플리케이션의 중심에 있고, 움직이는 부분도 많죠.
저는 사이드바 만드는 걸 좋아하지 않아요. 그래서 30개가 넘는 사이드바를 만들었어요. 온갖 방식의 구성으로요. 그 다음에 핵심 컴포넌트만 추려서 sidebar.tsx로 만들었어요.
이제 그 위에 쌓아 올릴 수 있는 탄탄한 기반이 생겼어요. 조립식이고, 테마를 입힐 수 있고, 커스터마이즈할 수 있어요.
설치하기
sidebar.tsx를 설치하기 위해 다음 명령어를 실행해 주세요.
npx shadcn@latest add sidebar
위 명령어가 색상을 자동으로 설치해 줄 거예요. 만약 설치되지 않았다면, 아래 코드를 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%;
}
}
색상에 대한 자세한 내용은 뒤의 테마 섹션에서 다룰게요.
@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를 여닫는 트리거예요.
사용하기
레이아웃의 루트를 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>
)
}
첫 번째 사이드바 만들기
가장 기본적인 사이드바부터 시작해 볼게요. 메뉴가 있는 접히는 사이드바예요.
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
접히는 사이드바를 렌더링하는 메인 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" />
}
<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>
)
}
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 /> 컴포넌트로 구성돼요.
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를 사용해서 프로젝트 목록을 렌더링하는 예시예요.
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)
- shadcn/ui 블록 라이브러리 — 사이드바를 응용한 실제 레이아웃 예시