Sidebar
먼저 보고, 그다음 계약을 읽습니다
앱 셸 좌측 세로 네비게이션 — 활성 항목 + 접이식 그룹.
Component intent의도와 경계 읽기
Radix 비의존 자체 구현. semantic <nav> + <ul> + collapsible 그룹 위에 controlled/uncontrolled 활성 상태와 그룹 펼침/접힘을 얹었다. items 배열로 data-driven 하게 쓰거나, SidebarItem/SidebarGroup 을 children 으로 조합(composition)해 쓴다 — 둘을 동시에 넘기지 말 것. collapsible=false 면 모든 그룹이 항상 펼쳐진 정적 헤더로 렌더된다. 보통 AppShell 의 sidebar 슬롯에 넣는다.
예제
SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.
import { Sidebar } from "@axe/ui";<Sidebar ariaLabel="Primary" value={current} onValueChange={setCurrent} defaultExpanded={{ proj: true }} items={[ { kind: "item", value: "home", label: "홈" }, { kind: "group", id: "proj", label: "프로젝트", items: [ { kind: "item", value: "frame", label: "frame" }, { kind: "item", value: "hive", label: "hive" }, ], }, ]}/>import { Sidebar, SidebarGroup, SidebarItem } from "@axe/ui";<Sidebar defaultValue="home"> <SidebarItem value="home">홈</SidebarItem> <SidebarGroup label="프로젝트" defaultOpen> <SidebarItem value="frame">frame</SidebarItem> <SidebarItem value="hive" href="/hive">hive</SidebarItem> </SidebarGroup></Sidebar><nav class="axe-sidebar" aria-label="Primary"> <ul class="axe-sidebar__list" role="list"> <li class="axe-sidebar__item axe-sidebar__item--active"> <a href="/home" class="axe-sidebar__link" aria-current="page"> <span class="axe-sidebar__label">홈</span> </a> </li> <li class="axe-sidebar__group"> <button type="button" class="axe-sidebar__group-header" aria-expanded="true" aria-controls="grp-proj"> <span class="axe-sidebar__group-label">프로젝트</span> <span class="axe-sidebar__chevron" aria-hidden="true">›</span> </button> <ul id="grp-proj" role="list" class="axe-sidebar__group-list"> <li class="axe-sidebar__item"> <a href="/frame" class="axe-sidebar__link"> <span class="axe-sidebar__label">frame</span> </a> </li> </ul> </li> </ul></nav>Props
TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
items | SidebarNode[] | — | — | Data-driven 항목 트리. SidebarItemNode({ kind: 'item', value, label, icon?, href?, disabled? }) 와 SidebarGroupNode({ kind: 'group', label, icon?, id?, items }) 의 배열. children 과 동시 사용 금지. |
value | string | — | — | 활성 항목 value (controlled). 주면 onValueChange 도 함께 줘야 한다(아니면 dev 경고). |
defaultValue | string | — | — | uncontrolled 초기 활성 value. |
onValueChange | (value: string) => void | — | — | 항목 선택 시 호출. |
collapsible | boolean | — | true | 그룹 접기/펴기 허용. false 면 그룹 헤더가 비-interactive <div> 로 항상 펼침. |
defaultExpanded | Record<string, boolean> | — | — | group.id 별 초기 펼침 상태. 미지정 시 모든 그룹 펼침(보수적). |
ariaLabel | string | — | "Sidebar" | 루트 <nav> 의 aria-label(스크린리더). |
children | React.ReactNode | — | — | composition 모드 — SidebarItem/SidebarGroup 을 직접 배치. items 대신 사용. |
...rest | Omit<React.HTMLAttributes<HTMLElement>, "onChange"> | — | — | 나머지는 루트 <nav> 로 전파. |
SidebarItem.value | string | 필수 | — | 활성 비교 키. |
SidebarItem.active | boolean | — | — | 명시적 active — Provider 없이 쓰거나 강제 표시할 때. 미지정 시 컨텍스트의 active===value 로 판정. |
SidebarItem.href | string | — | — | 주면 <a>, 없으면 <button> 으로 렌더. |
SidebarItem.icon | React.ReactNode | — | — | 라벨 앞 아이콘(aria-hidden). |
SidebarItem.disabled | boolean | — | — | 비활성. 클릭 무시 + aria-disabled/disabled. |
SidebarItem.onSelect | (value: string) => void | — | — | 선택 시 항목 로컬 콜백(컨텍스트 setActive 와 별개). |
SidebarGroup.label | React.ReactNode | 필수 | — | 그룹 헤더 라벨. |
SidebarGroup.id | string | — | — | defaultExpanded 매핑 키. 미지정 시 label 의 string 값. |
SidebarGroup.defaultOpen | boolean | — | true | uncontrolled 초기 펼침. |
SidebarGroup.open | boolean | — | — | controlled 펼침 상태. |
SidebarGroup.onOpenChange | (open: boolean) => void | — | — | 펼침 토글 시 호출. |
.axe-* 클래스 계약
React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.
| 클래스 | 안정성 | 용도 |
|---|---|---|
.axe-sidebar | stable | 루트 <nav>. |
.axe-sidebar__list | stable | 최상위 항목 <ul role="list">. |
.axe-sidebar__item | stable | 단일 항목 <li>. |
.axe-sidebar__item--active | stable | 활성 항목 <li>. |
.axe-sidebar__item--disabled | stable | 비활성 항목 <li>. |
.axe-sidebar__link | stable | 항목의 클릭 대상 <a>/<button>. |
.axe-sidebar__icon | stable | 항목/그룹 앞 아이콘 span. |
.axe-sidebar__label | stable | 항목 라벨 텍스트 span. |
.axe-sidebar__group | stable | 그룹 <li>. |
.axe-sidebar__group-header | stable | 그룹 토글 <button>(collapsible) 헤더. |
.axe-sidebar__group-header--static | stable | 비-collapsible 그룹 헤더 <div>(토글 불가). |
.axe-sidebar__group-label | stable | 그룹 헤더 라벨 span. |
.axe-sidebar__group-list | stable | 그룹 하위 항목 패널 <ul>. 접힘 시 hidden 속성. |
.axe-sidebar__chevron | internal | 그룹 펼침 표시 chevron span(장식). |
접근성
키보드, ARIA, 구현 노트를 함께 검토합니다.
- Tab — 링크/버튼/그룹 헤더를 문서 순서로 이동(자체 roving tabindex 없음)
- Enter/Space — 그룹 헤더에서 펼침/접힘(네이티브 <button>)
루트 <nav aria-label> 안에 <ul role="list">. 활성 항목의 <a>/<button> 에 aria-current="page". 그룹 헤더는 <button aria-expanded aria-controls={panelId}>, 하위 패널 <ul> 은 접힘 시 hidden 속성으로 AX 트리에서 제외. disabled 항목은 <button disabled> 또는 <a aria-disabled="true">.
controlled 로 쓸 땐 value 와 onValueChange 를 반드시 함께 준다(하나만 주면 dev 콘솔 경고 + 선택이 silent 무시). collapsible=false 면 그룹 헤더가 <div class="...--static"> 로 렌더되어 포커스/토글 불가.