Tree
먼저 보고, 그다음 계약을 읽습니다
재귀 collapsible 트리 — 키보드 nav·단일 선택·텍스트 필터 내장(Radix 무의존).
Component intent의도와 경계 읽기
중첩 nodes 를 role="tree"/treeitem 시맨틱으로 직접 그리는 트리 뷰. 펼침은 controlled(expanded/onExpandedChange) 또는 uncontrolled(defaultExpanded) 로, 선택은 단일 controlled(selected/onSelectedChange) 로 관리한다. filter 문자열(대소문자 무시)을 주면 매칭 노드 + 모든 조상 + 매칭 노드의 모든 자손이 가시화되고 조상 chain 은 자동 펼쳐진다. ↑↓←→/Home/End/Enter 전 키보드 상호작용과 roving tabindex 를 자체 구현. renderLabel 로 라벨 영역을 커스터마이즈할 수 있다.
예제
SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.
import { Tree } from "@axe/ui";import { useState } from "react";function FileTree() { const [selected, setSelected] = useState<string>(); const [query, setQuery] = useState(""); return ( <Tree nodes={[ { id: "a", label: "프로젝트", children: [ { id: "a1", label: "frame" }, { id: "a2", label: "hive" }, ], }, ]} defaultExpanded={["a"]} selected={selected} onSelectedChange={setSelected} filter={query} ariaLabel="파일 트리" /> );}<ul class="axe-tree" role="tree" aria-label="파일 트리"> <li class="axe-tree__item" role="treeitem" aria-level="1" aria-expanded="true" tabindex="0"> <div class="axe-tree__row"> <button type="button" class="axe-tree__chevron" tabindex="-1" aria-label="접기">▾</button> <span class="axe-tree__label">프로젝트</span> </div> <ul class="axe-tree__group" role="group"> <li class="axe-tree__item axe-tree__item--selected" role="treeitem" aria-level="2" aria-selected="true" tabindex="-1"> <div class="axe-tree__row" style="padding-left: 16px"> <span class="axe-tree__chevron axe-tree__chevron--leaf" aria-hidden="true"></span> <span class="axe-tree__label">frame</span> </div> </li> </ul> </li></ul>Props
TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
nodes | TreeNode[] | 필수 | — | 트리 데이터. `{ id; label: ReactNode; icon?; children?: TreeNode[]; data?: unknown; disabled? }`. children 이 있으면 chevron 이 붙는다. |
defaultExpanded | string[] | — | — | uncontrolled 초기 펼침 id 목록. |
expanded | string[] | — | — | controlled 펼침 id 목록. 주면 내부 상태 대신 이 값을 쓴다. |
onExpandedChange | (ids: string[]) => void | — | — | 펼침 변경 콜백(chevron 클릭·←/→ 키). |
selected | string | — | — | controlled 단일 선택 id. 매칭 노드에 aria-selected + __item--selected. |
onSelectedChange | (id: string) => void | — | — | 선택 변경 콜백(행 클릭·Enter/Space). disabled 노드는 발화하지 않음. |
renderLabel | (node: TreeNode, state: { isExpanded: boolean; isSelected: boolean; level: number }) => React.ReactNode | — | — | 라벨 영역 커스텀 렌더. 미지정 시 node.label 을 그대로. |
indent | number | — | 16 | depth 당 들여쓰기 px(row 의 inline paddingLeft = (level-1)*indent). |
filter | string | — | "" | 텍스트 필터(대소문자 무시). label 이 문자열인 노드만 매칭 대상. |
ariaLabel | string | — | "Tree" | 루트 <ul role="tree"> 의 aria-label. |
...rest | Omit<React.HTMLAttributes<HTMLUListElement>, "onChange"> | — | — | className 병합, 나머지는 루트 <ul> 로 전파. ref 는 루트 ul 로 forward. |
.axe-* 클래스 계약
React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.
| 클래스 | 안정성 | 용도 |
|---|---|---|
.axe-tree | stable | 루트 <ul role="tree">. |
.axe-tree__item | stable | <li role="treeitem"> — 한 노드. |
.axe-tree__item--selected | stable | 선택된 노드(배경/텍스트 강조). |
.axe-tree__item--disabled | stable | 비활성 노드(흐림·클릭 무시). |
.axe-tree__row | stable | 클릭 영역 — chevron+icon+label 을 담고 inline paddingLeft 로 들여쓰기. |
.axe-tree__chevron | stable | 펼침 토글 <button>(tabIndex -1). |
.axe-tree__chevron--leaf | stable | 자식 없는 노드의 chevron 자리 정렬용 빈 spacer. |
.axe-tree__icon | stable | 라벨 앞 아이콘 슬롯(aria-hidden). |
.axe-tree__label | stable | 노드 라벨 텍스트 영역. |
.axe-tree__group | stable | 자식들의 중첩 <ul role="group">. |
접근성
키보드, ARIA, 구현 노트를 함께 검토합니다.
- ↑/↓ — 가시 노드 사이 이동
- ← — 펼쳐진 노드면 접고, 아니면 부모로 이동
- → — 자식 있고 닫혔으면 펼치고, 열렸으면 첫 자식으로
- Enter/Space — 선택(선택 콜백 발화)
- Home/End — 처음/마지막 가시 노드
루트 <ul role="tree" aria-label>, 각 노드 <li role="treeitem"> 에 aria-level, 자식 있으면 aria-expanded, 선택 시 aria-selected, 비활성 시 aria-disabled. 자식 묶음은 <ul role="group">. chevron 버튼은 aria-label "접기"/"펼치기".
roving tabindex — 활성 treeitem 만 tabIndex 0, 나머지는 -1. 활성 노드가 정해지기 전엔 root 가 tab 진입점이며 focus 시 첫 treeitem 으로 위임. activeId 변경 시 트리 내부에 focus 가 있을 때만 해당 항목으로 focus 이동(외부 클릭 도난 방지). filter 활성 시 매칭 조상 chain 은 강제 펼쳐진다(effective expanded).