# Tree view

> A hierarchy of expandable rows with arrow-key navigation, typeahead and cascading checkboxes.

URL: https://apogee.avaui.dev/docs/tree-view
Last updated: 2026-10-15

A hierarchy of expandable rows with arrow-key navigation, typeahead and cascading checkboxes.

Last updated October 15, 2026

Uses JavaScript

Adds the tree pattern: one tab stop, arrow keys, Home and End, typeahead, and cascading checkboxes.

**Without JS:** Branches still open and close (they are native details elements) and links still work. The list has no tree role and arrow keys do nothing; Tab moves through the rows, and checkboxes check one at a time without cascading.

```astro
<TreeView label="Project files">
<TreeItem label="src">
  <Folder slot="start" />
  <TreeItem label="components"><Folder slot="start" /><TreeItem label="Button.astro"><FileCode slot="start" /></TreeItem></TreeItem>
  <TreeItem label="index.ts"><FileCode slot="start" /></TreeItem>
</TreeItem>
<TreeItem label="README.md"><FileText slot="start" /></TreeItem>
</TreeView>
```

## Add

```sh
npx @ava-ui/apogee add tree-view
```

Depends on `utils` (`island.ts`). Uses `@lucide/astro` for the chevron.

## Usage

```astro
---
import TreeView from '@/components/apogee/TreeView/TreeView.astro'
import TreeItem from '@/components/apogee/TreeView/TreeItem.astro'
---
<TreeView label="Categories">
  <TreeItem label="Clothing" open>
    <TreeItem label="Shirts" href="/c/shirts" />
  </TreeItem>
</TreeView>
```

## Anatomy

-   **TreeView** — a `<ul>` with the accessible name `label`. The island turns it into `role="tree"`.
-   **TreeItem** — an `<li>`. With children it is a branch: a native `<details>` whose `<summary>` is the row, and a nested list. Without children it is a leaf row, a link when `href` is set.
-   **Row** — chevron (branches), checkbox (when `value` is set), `start` slot (icon), label, `end` slot (an action, a count).

## API

TreeView:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | — | Required. Accessible name of the tree. |
| `name` | `string` | — | Form name given to every item checkbox (JS). Set `name` on each TreeItem for forms that must work without JS. |
| `lines` | `boolean` | `true` | A guide line down each nested group. |
| `class` | `string` | — | Extra classes on the `<ul>`. |

TreeItem:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | — | Required. Row text and the item’s accessible name. |
| `href` | `string` | — | Makes a leaf a link. |
| `value` | `string` | — | Adds a checkbox with this value. |
| `name` | `string` | — | Checkbox form name. |
| `checked` | `boolean` | `false` | Initial checkbox state. |
| `open` | `boolean` | `false` | A branch starts open. |
| `disabled` | `boolean` | `false` | Dims the row; it cannot be checked, toggled with Enter or Space, clicked open, or followed as a link. |
| `class` | `string` | — | Extra classes on the `<li>`. |

## Variants

Without guide lines, with counts in the `end` slot:

```astro
<TreeView label="Mail" lines={false}>
<TreeItem label="Inbox" open>
  <Badge slot="end" size="small">12</Badge>
  <TreeItem label="Team"><Badge slot="end" size="small">3</Badge></TreeItem>
</TreeItem>
</TreeView>
```

## Selection

Items with a `value` get a checkbox. Checking a branch checks everything under it; a partly checked branch shows as indeterminate. The tree dispatches `apogee:tree-select` (`detail: { values }`).

```astro
<TreeView label="Permissions" name="perm">
<TreeItem label="Projects" value="projects" open>
  <TreeItem label="Read" value="projects.read" />
  <TreeItem label="Write" value="projects.write" />
</TreeItem>
</TreeView>
```

Saved state renders correctly on load: a pre-checked child makes its parent mixed. A disabled item cannot be checked, opened or followed.

```astro
<TreeView label="Saved permissions">
<TreeItem label="Reports" value="reports" open>
  <TreeItem label="View" value="reports.view" checked />
  <TreeItem label="Export" value="reports.export" />
  <TreeItem label="Admin console" href="#" disabled />
</TreeItem>
</TreeView>
```

## States

-   **Open / closed** — the native `open` attribute on the branch; the chevron turns; `aria-expanded` follows it.
-   **Focused** — a ring on the row of the focused item.
-   **Checked / indeterminate** — native checkbox states, set by the cascade.
-   **Disabled** — dimmed; no pointer input, Enter and Space do nothing, its checkbox is skipped by the cascade.

## Examples

A navigation tree of links:

```astro
<TreeView label="Docs">
<TreeItem label="Guides" open>
  <TreeItem label="Install" href="#" />
  <TreeItem label="Theming" href="#" />
</TreeItem>
</TreeView>
```

## Keyboard

| Key | Action |
| --- | --- |
| `Tab` | Moves into the tree (one stop) and out of it. |
| `↓` / `↑` | Next / previous visible item. |
| `→` | Opens a closed branch; on an open branch, moves to its first child. |
| `←` | Closes an open branch; otherwise moves to the parent. |
| `Home` / `End` | First / last visible item. |
| `Enter` | Toggles a branch; follows a link. |
| `Space` | Toggles the checkbox, or the branch when there is none. |
| Letters | Typeahead: moves to the next item whose label starts with the typed text. |

## Accessibility

-   With JS the list is `role="tree"`, items are `treeitem` with `aria-level`, `aria-expanded` on branches, `aria-checked` (true, false or mixed) on items with a checkbox, and nested lists are `group`. A click on a row hands focus to its item, so the arrow keys keep working. Each item is named by its `label`, so a branch is not read with its children’s text.
-   Without JS there is no tree role: arrow keys would not work, so the markup does not promise them.
-   In RTL, `→` and `←` swap.

## Tokens

| Token | Used for |
| --- | --- |
| `--foreground-neutral` / `--foreground-neutral-faded` | Row text / icons and chevron |
| `--background-neutral-faded-highlighted` | Row hover |
| `--border-neutral` | Guide lines |
| `--outline-color-focus` | Focus ring |
| `--background-primary` | Checkbox accent |

## Do / Don’t

-   **Do** use a tree for real hierarchies a user navigates or selects in.
-   **Do** keep labels short and unique among siblings; typeahead matches them.
-   **Don’t** use a tree for site navigation with two levels; NavGroup in a Sidebar reads better.
-   **Don’t** put more than one interactive control in a row’s `end` slot.

## Content

-   Labels are nouns: file names, category names, permission names.
-   Sort siblings in a stable order (alphabetical or by importance) so typeahead and scanning work.

## Related

[

NavLinkA navigation link that auto-detects and styles the current route, with prefix matching, external links and a collapsible NavGroup.

](https://apogee.avaui.dev/docs/nav-link)[

AccordionA stack of collapsible disclosure panels; expand one to read it, collapse it to reclaim space.

](https://apogee.avaui.dev/docs/accordion)[

CheckboxGroupA fieldset of Checkbox controls with one legend, hint and error.

](https://apogee.avaui.dev/docs/checkbox-group)[

CollapsibleA single show/hide region, built on native <details>. Zero JS.

](https://apogee.avaui.dev/docs/collapsible)
