TreeView
A hierarchical tree structure with expandable branches and selectable items. Implements the full WAI-ARIA TreeView keyboard pattern including typeahead, expand-all-siblings (*), and nested navigation.
Anatomy
<TreeView.Provider bind:expandedValue bind:selectedValue>
<TreeView.Branch value="docs" depth={0}>
<TreeView.BranchControl>
<TreeView.BranchIndicator>▶</TreeView.BranchIndicator>
<TreeView.Label>Documents</TreeView.Label>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.Item value="readme" depth={1}>README.md</TreeView.Item>
<TreeView.Item value="license" depth={1}>LICENSE</TreeView.Item>
<TreeView.Branch value="src" depth={1}>
<TreeView.BranchControl>
<TreeView.BranchIndicator>▶</TreeView.BranchIndicator>
<TreeView.Label>src</TreeView.Label>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.Item value="index" depth={2}>index.ts</TreeView.Item>
</TreeView.BranchContent>
</TreeView.Branch>
</TreeView.BranchContent>
</TreeView.Branch>
</TreeView.Provider>
Branches can nest arbitrarily deep. Leaf items use Item, expandable folders use Branch.
Parts
| Part |
Element |
Description |
Provider |
<div> |
Root container. role="tree". Manages expand/select state, keyboard. |
Branch |
<div> |
Expandable node. Contains BranchControl + BranchContent. |
BranchControl |
<div> |
Clickable row for a branch. role="treeitem". Handles selection + expand. |
BranchContent |
<div> |
Children container. role="group". Hidden when collapsed. |
BranchIndicator |
<div> |
Expand/collapse icon (e.g. arrow). Clicking toggles expand. Decorative. |
BranchIndentGuide |
<div> |
Visual indent line. Decorative (aria-hidden). |
Item |
<div> |
Leaf node. role="treeitem". Selectable. |
Label |
<div> |
Text label for branch or item. |
ARIA
| Part |
Attribute |
Value |
| Provider |
role |
tree |
| Provider |
aria-multiselectable |
true (when selectionMode='multiple') |
| Provider |
aria-label |
Configurable (default "Tree") |
| BranchControl |
role |
treeitem |
| BranchControl |
aria-expanded |
true | false |
| BranchControl |
aria-selected |
true | false |
| BranchControl |
aria-level |
Depth + 1 (1-based) |
| BranchControl |
aria-disabled |
true (when disabled) |
| BranchContent |
role |
group |
| Item |
role |
treeitem |
| Item |
aria-selected |
true | false |
| Item |
aria-level |
Depth + 1 (1-based) |
| Item |
aria-disabled |
true (when disabled) |
| BranchIndicator |
aria-hidden |
true |
| BranchIndentGuide |
aria-hidden |
true |
Data Attributes
| Part |
Attribute |
Values |
| Provider |
data-tree-view |
Always present |
| Branch |
data-tree-view-branch |
Always present |
| Branch |
data-value |
Branch value string |
| Branch |
data-state |
open | closed |
| Branch |
data-selected |
Present when selected |
| Branch |
data-disabled |
Present when disabled |
| Branch |
data-depth |
Nesting depth (0-based) |
| BranchControl |
data-tree-view-branch-control |
Always present |
| BranchControl |
data-value |
Branch value |
| BranchControl |
data-state |
open | closed |
| BranchControl |
data-selected |
Present when selected |
| BranchControl |
data-disabled |
Present when disabled |
| BranchControl |
data-depth |
Nesting depth |
| BranchContent |
data-tree-view-branch-content |
Always present |
| BranchContent |
data-state |
open | closed |
| BranchContent |
data-depth |
Nesting depth |
| BranchIndicator |
data-tree-view-branch-indicator |
Always present |
| BranchIndicator |
data-state |
open | closed |
| BranchIndentGuide |
data-tree-view-branch-indent-guide |
Always present |
| BranchIndentGuide |
data-depth |
Nesting depth |
| Item |
data-tree-view-item |
Always present |
| Item |
data-value |
Item value string |
| Item |
data-selected |
Present when selected |
| Item |
data-disabled |
Present when disabled |
| Item |
data-depth |
Nesting depth |
CSS Variables
| Variable |
Part |
Description |
--soma-tree-depth |
Branch, Item |
Nesting depth (0-based). Use for indentation: padding-left: calc(var(--soma-tree-depth) * 1rem) |
Keyboard
| Key |
Action |
ArrowDown |
Move focus to next visible item |
ArrowUp |
Move focus to previous visible item |
ArrowRight |
Expand a collapsed branch, or move to first child if expanded |
ArrowLeft |
Collapse an expanded branch, or move to parent if leaf/collapsed |
Home |
Move focus to first item |
End |
Move focus to last visible item |
Enter / Space |
Select/deselect the focused item |
* |
Expand all siblings of the focused item |
| Character keys |
Typeahead — focus first matching item by text content |
Behavior
Expand/collapse
Branches expand/collapse via:
- Arrow Right/Left keys
- Clicking the BranchIndicator
- Clicking BranchControl (when
expandOnClick is true, default)
* key to expand all siblings at the same level
Selection
single mode: one item selected at a time (clicking a new item deselects the previous)
multiple mode: clicking toggles individual selection
Typeahead
When typeahead is true (default), typing characters focuses the first visible item whose text starts with the typed string. Buffer clears after 500ms of inactivity.
Depth tracking
Each Branch and Item receives a depth prop (0-based). This becomes:
aria-level (1-based, as required by ARIA spec)
data-depth attribute
--soma-tree-depth CSS variable for indentation
Usage
Basic file tree
<script>
import { TreeView } from '$soma/components';
let expanded = $state(['src']);
let selected = $state<string[]>([]);
</script>
<TreeView.Provider bind:expandedValue={expanded} bind:selectedValue={selected}>
<TreeView.Item value="readme" depth={0}>README.md</TreeView.Item>
<TreeView.Branch value="src" depth={0}>
<TreeView.BranchControl>
<TreeView.BranchIndicator>▶</TreeView.BranchIndicator>
src
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.Item value="index" depth={1}>index.ts</TreeView.Item>
<TreeView.Item value="app" depth={1}>App.svelte</TreeView.Item>
</TreeView.BranchContent>
</TreeView.Branch>
</TreeView.Provider>
Multiple selection
<TreeView.Provider selectionMode="multiple" bind:selectedValue>
<!-- Multiple items can be selected -->
</TreeView.Provider>
Indentation via CSS variable
[data-tree-view-branch-control],
[data-tree-view-item] {
padding-left: calc(var(--soma-tree-depth) * 1.5rem + 0.5rem);
}
Comparison with reference libraries
| Feature |
Ark UI |
Radix Vue |
React Aria |
Soma |
| Parts |
Root, Branch, BranchControl, BranchContent, BranchIndicator, BranchIndentGuide, Item, Label |
Root, Item (flat) |
Tree, TreeItem |
Provider, Branch, BranchControl, BranchContent, BranchIndicator, BranchIndentGuide, Item, Label |
| Selection single/multiple |
Yes |
Yes |
Yes |
Yes |
role="tree" / role="treeitem" |
Yes |
Yes |
Yes |
Yes |
aria-expanded |
Yes |
Yes |
Yes |
Yes |
aria-selected |
Yes |
Yes |
Yes |
Yes |
aria-level |
Yes |
Yes |
No (CSS var) |
Yes |
aria-setsize / aria-posinset |
Yes |
Yes |
Yes |
No (consumer can add) |
| Arrow Up/Down navigate |
Yes |
Yes |
Yes |
Yes |
| Arrow Right expand / first child |
Yes |
Yes |
Yes |
Yes |
| Arrow Left collapse / parent |
Yes |
Yes |
Yes |
Yes |
| Home/End |
Yes |
Yes |
Yes |
Yes |
* expand all siblings |
Yes |
No |
No |
Yes |
| Typeahead |
Yes |
No |
No |
Yes |
--depth CSS variable |
Yes |
data-indent |
--tree-item-level |
Yes (--soma-tree-depth) |
data-state open/closed |
Yes |
Yes |
No |
Yes |
data-selected |
Yes |
Yes |
No |
Yes |
data-depth |
No |
Yes (data-indent) |
No |
Yes |
expandOnClick |
Yes |
No |
No |
Yes |
dir (RTL) |
Yes |
Yes |
No |
Yes |
| BranchIndentGuide |
Yes |
No |
No |
Yes |
| Inline renaming |
Yes (F2) |
No |
No |
No (future) |
| Async child loading |
Yes |
No |
No |
No (future) |
| Drag & drop |
No |
No |
Yes |
No (future) |
| Checkbox mode |
Yes |
No |
No |
No (future) |