본문으로 건너뛰기
데이터 / tree

Tree

재귀 collapsible 트리 — 키보드 nav·단일 선택·텍스트 필터 내장(Radix 무의존).
데이터treeStatic specimentreetreeview트리폴더
01 · Specimen

먼저 보고, 그다음 계약을 읽습니다

재귀 collapsible 트리 — 키보드 nav·단일 선택·텍스트 필터 내장(Radix 무의존).

Workbench 불러오는 중…
Component intent의도와 경계 읽기

중첩 nodes 를 role="tree"/treeitem 시맨틱으로 직접 그리는 트리 뷰. 펼침은 controlled(expanded/onExpandedChange) 또는 uncontrolled(defaultExpanded) 로, 선택은 단일 controlled(selected/onSelectedChange) 로 관리한다. filter 문자열(대소문자 무시)을 주면 매칭 노드 + 모든 조상 + 매칭 노드의 모든 자손이 가시화되고 조상 chain 은 자동 펼쳐진다. ↑↓←→/Home/End/Enter 전 키보드 상호작용과 roving tabindex 를 자체 구현. renderLabel 로 라벨 영역을 커스터마이즈할 수 있다.

02 · Use

예제

SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.

01선택 + 필터 (controlled)tsxfragment
선택 + 필터 (controlled)
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="파일 트리"    />  );}
02CSS-only 정적 스냅샷 (비-React)htmlfragment
CSS-only 정적 스냅샷 (비-React)
<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>
03 · React

Props

TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.

이름타입필수기본값설명
nodesTreeNode[]필수트리 데이터. `{ id; label: ReactNode; icon?; children?: TreeNode[]; data?: unknown; disabled? }`. children 이 있으면 chevron 이 붙는다.
defaultExpandedstring[]uncontrolled 초기 펼침 id 목록.
expandedstring[]controlled 펼침 id 목록. 주면 내부 상태 대신 이 값을 쓴다.
onExpandedChange(ids: string[]) => void펼침 변경 콜백(chevron 클릭·←/→ 키).
selectedstringcontrolled 단일 선택 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 을 그대로.
indentnumber16depth 당 들여쓰기 px(row 의 inline paddingLeft = (level-1)*indent).
filterstring""텍스트 필터(대소문자 무시). label 이 문자열인 노드만 매칭 대상.
ariaLabelstring"Tree"루트 <ul role="tree"> 의 aria-label.
...restOmit<React.HTMLAttributes<HTMLUListElement>, "onChange">className 병합, 나머지는 루트 <ul> 로 전파. ref 는 루트 ul 로 forward.
04 · Any stack

.axe-* 클래스 계약

React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.

클래스안정성용도
.axe-treestable루트 <ul role="tree">.
.axe-tree__itemstable<li role="treeitem"> — 한 노드.
.axe-tree__item--selectedstable선택된 노드(배경/텍스트 강조).
.axe-tree__item--disabledstable비활성 노드(흐림·클릭 무시).
.axe-tree__rowstable클릭 영역 — chevron+icon+label 을 담고 inline paddingLeft 로 들여쓰기.
.axe-tree__chevronstable펼침 토글 <button>(tabIndex -1).
.axe-tree__chevron--leafstable자식 없는 노드의 chevron 자리 정렬용 빈 spacer.
.axe-tree__iconstable라벨 앞 아이콘 슬롯(aria-hidden).
.axe-tree__labelstable노드 라벨 텍스트 영역.
.axe-tree__groupstable자식들의 중첩 <ul role="group">.
비-React 소비 노트
스타일(.axe-tree*)은 순수 CSS 라 서버가 role/aria 를 갖춘 정적 트리 마크업을 그리면 시각은 그대로 재현된다. 단 들여쓰기는 row 의 inline paddingLeft(=(level-1)*indent)로 들어가므로 CSS-only 소비자는 depth 별 padding-left 를 직접 넣어야 하고, 펼침/접힘·키보드 nav·필터 자동펼침은 React 컴포넌트(또는 동등 JS) 없이는 동작하지 않는다 — html 예제는 한 상태의 스냅샷일 뿐.
05 · Inclusive

접근성

키보드, ARIA, 구현 노트를 함께 검토합니다.

Keyboard
  • ↑/↓ — 가시 노드 사이 이동
  • ← — 펼쳐진 노드면 접고, 아니면 부모로 이동
  • → — 자식 있고 닫혔으면 펼치고, 열렸으면 첫 자식으로
  • Enter/Space — 선택(선택 콜백 발화)
  • Home/End — 처음/마지막 가시 노드
ARIA

루트 <ul role="tree" aria-label>, 각 노드 <li role="treeitem"> 에 aria-level, 자식 있으면 aria-expanded, 선택 시 aria-selected, 비활성 시 aria-disabled. 자식 묶음은 <ul role="group">. chevron 버튼은 aria-label "접기"/"펼치기".

Notes

roving tabindex — 활성 treeitem 만 tabIndex 0, 나머지는 -1. 활성 노드가 정해지기 전엔 root 가 tab 진입점이며 focus 시 첫 treeitem 으로 위임. activeId 변경 시 트리 내부에 focus 가 있을 때만 해당 항목으로 focus 이동(외부 클릭 도난 방지). filter 활성 시 매칭 조상 chain 은 강제 펼쳐진다(effective expanded).

06 · Judgment

권장 · 지양

권장
  • 필터 대상은 label 이 문자열인 노드뿐 — 검색 가능한 라벨은 문자열로 둘 것.
  • controlled 펼침을 쓸 땐 expanded 와 onExpandedChange 를 함께.
  • 선택 상태는 부모에서 소유(selected/onSelectedChange) — 단일 선택 모델.
지양
  • 다중 선택을 기대하지 말 것 — selected 는 단일 id.
  • disabled 노드에 선택 로직을 걸지 말 것(콜백이 발화하지 않음).
검색창을 열면 컴포넌트 인덱스를 불러옵니다.