---
outline: deep
---

# Tree

Tree views present hierarchical data like file explorers, navigation menus, and taxonomies. Each node expands/collapses to reveal nested items and can be selected in several ways.

**`<l-tree>`** — Custom Element · Shadow DOM

## Options

### Basic

Wrap `<l-tree-item>` nodes inside `<l-tree>`. Nested `<l-tree-item>` children become sub-nodes automatically.

```html
<l-tree>
  <l-tree-item>
    Documents
    <l-tree-item>
      Photos
      <l-tree-item>beach.jpg</l-tree-item>
      <l-tree-item>mountain.jpg</l-tree-item>
    </l-tree-item>
    <l-tree-item>
      Invoices
      <l-tree-item>january.pdf</l-tree-item>
      <l-tree-item>february.pdf</l-tree-item>
    </l-tree-item>
  </l-tree-item>
  <l-tree-item>
    Projects
    <l-tree-item>luxen-ui</l-tree-item>
    <l-tree-item>website</l-tree-item>
  </l-tree-item>
  <l-tree-item>Downloads</l-tree-item>
</l-tree>
```

### Custom icons

Place any element in the `prefix` slot to render a leading icon before the label. Icons inherit the current text color.

```html
<l-tree>
  <l-tree-item expanded>
    <l-icon
      slot="prefix"
      name="lucide:folder-open"
    ></l-icon>
    src
    <l-tree-item>
      <l-icon
        slot="prefix"
        name="lucide:folder"
      ></l-icon>
      components
      <l-tree-item>
        <l-icon
          slot="prefix"
          name="lucide:file-code"
        ></l-icon>
        Button.ts
      </l-tree-item>
      <l-tree-item>
        <l-icon
          slot="prefix"
          name="lucide:file-code"
        ></l-icon>
        Tree.ts
      </l-tree-item>
    </l-tree-item>
    <l-tree-item>
      <l-icon
        slot="prefix"
        name="lucide:file-json"
      ></l-icon>
      package.json
    </l-tree-item>
    <l-tree-item>
      <l-icon
        slot="prefix"
        name="lucide:file-text"
      ></l-icon>
      README.md
    </l-tree-item>
  </l-tree-item>
  <l-tree-item>
    <l-icon
      slot="prefix"
      name="lucide:folder"
    ></l-icon>
    public
    <l-tree-item>
      <l-icon
        slot="prefix"
        name="lucide:image"
      ></l-icon>
      logo.svg
    </l-tree-item>
  </l-tree-item>
</l-tree>
```

### Custom expand icons

Override the `expand-icon` and `collapse-icon` slots to show a different icon per state — e.g. a closed folder when the branch is collapsed and an open folder when expanded. Leaves keep the `prefix` slot for their own icon.

```html
<l-tree>
  <l-tree-item expanded>
    <l-icon
      slot="expand-icon"
      name="lucide:folder"
    ></l-icon>
    <l-icon
      slot="collapse-icon"
      name="lucide:folder-open"
    ></l-icon>
    src
    <l-tree-item>
      <l-icon
        slot="expand-icon"
        name="lucide:folder"
      ></l-icon>
      <l-icon
        slot="collapse-icon"
        name="lucide:folder-open"
      ></l-icon>
      components
      <l-tree-item>
        <l-icon
          slot="prefix"
          name="lucide:file-code"
        ></l-icon>
        Button.ts
      </l-tree-item>
      <l-tree-item>
        <l-icon
          slot="prefix"
          name="lucide:file-code"
        ></l-icon>
        Tree.ts
      </l-tree-item>
    </l-tree-item>
    <l-tree-item>
      <l-icon
        slot="prefix"
        name="lucide:file-json"
      ></l-icon>
      package.json
    </l-tree-item>
    <l-tree-item>
      <l-icon
        slot="prefix"
        name="lucide:file-text"
      ></l-icon>
      README.md
    </l-tree-item>
  </l-tree-item>
</l-tree>
```

### Multiple selection

Set `selection="multiple"` to render a native checkbox on every item. Toggling a parent cascades the selection to its descendants and sets the indeterminate state when only some children are selected.

```html
<l-tree selection="multiple">
  <l-tree-item expanded>
    Fruits
    <l-tree-item>Apple</l-tree-item>
    <l-tree-item selected>Banana</l-tree-item>
    <l-tree-item>Cherry</l-tree-item>
  </l-tree-item>
  <l-tree-item>
    Vegetables
    <l-tree-item>Carrot</l-tree-item>
    <l-tree-item>Lettuce</l-tree-item>
  </l-tree-item>
</l-tree>
```

### Independent selection

Add `independent` to decouple parents and children: a parent can be selected without ticking any of its descendants and vice-versa. Useful when nodes represent independent concepts (categories, tags, permissions) rather than aggregations.

```html
<l-tree
  selection="multiple"
  independent
>
  <l-tree-item expanded>
    Region: Europe
    <l-tree-item>France</l-tree-item>
    <l-tree-item>Germany</l-tree-item>
    <l-tree-item>Italy</l-tree-item>
  </l-tree-item>
  <l-tree-item expanded>
    Region: Americas
    <l-tree-item>Brazil</l-tree-item>
    <l-tree-item>United States</l-tree-item>
  </l-tree-item>
</l-tree>
```

### Leaf-only selection

Set `selection="leaf"` when only terminal nodes represent selectable values. Clicking a branch only toggles its expansion.

```html
<l-tree selection="leaf">
  <l-tree-item expanded>
    Components
    <l-tree-item>Button</l-tree-item>
    <l-tree-item>Dropdown</l-tree-item>
    <l-tree-item>Tree</l-tree-item>
  </l-tree-item>
  <l-tree-item>
    Tokens
    <l-tree-item>Colors</l-tree-item>
    <l-tree-item>Spacing</l-tree-item>
  </l-tree-item>
</l-tree>
```

### Disabled items

Add `disabled` to any item to prevent selection and interaction. The item remains visible and part of the structure.

```html
<l-tree selection="multiple">
  <l-tree-item expanded>
    Settings
    <l-tree-item>Profile</l-tree-item>
    <l-tree-item disabled>Billing (locked)</l-tree-item>
    <l-tree-item>Notifications</l-tree-item>
  </l-tree-item>
</l-tree>
```

### Lazy loading

Add `lazy` to an item whose children will be fetched on first expand. The component emits `lazy-load`; set `loading` to render a spinner in place of the chevron, then append children and remove `lazy`.

## Examples

### Row actions

Place any interactive element inside a `<l-tree-item>` to expose per-row actions (e.g. an `<l-dropdown>` menu, a button, a link). Clicks on `<button>`, `<a>`, `<input>` and elements with `role="button"` or `role="menuitem"` never toggle the row's selection or expansion.

This demo is controlled from Vue state: the yellow `…` trigger is rendered only on the selected row via `v-if`, so clicking another row moves the button there without duplicating it in the DOM.

### Project roadmap

A roadmap of phases and tasks, where collapsing folds away phase details for a high-level overview. Demonstrates the `prefix` slot (per-row icon), the default slot (title), and the `content` slot (block-level description below the row — visible for leaves, hidden when a branch is collapsed). The `::part(base)` and `::part(label)` are overridden to top-align rows and let descriptions wrap.

## Accessibility

### Criteria

- **Role** — Container has `role="tree"`, items have `role="treeitem"`, groups have `role="group"`
- **Expanded state** — Branches expose `aria-expanded` reflecting open state. Leaf nodes omit the attribute.
- **Selected state** — Selected items expose `aria-selected="true"`
- **Multi-selectable** — Container sets `aria-multiselectable="true"` when `selection="multiple"`
- **Disabled state** — Disabled items expose `aria-disabled="true"` and stay in the DOM for discoverability
- **Focus management** — Roving tabindex: only one item is in the tab order at a time; arrow keys move focus within the tree
- **Motion** — Chevron rotation and spinner respect `prefers-reduced-motion`

### Rules

- Every item needs visible text content for its accessible name
- Use `selection="leaf"` when branches are never valid values — this prevents keyboard users from accidentally selecting aggregations
- Prefer `selection="multiple" independent` for forms where parent selection is a distinct choice from the children

### Keyboard interactions

- `ArrowDown` — Moves focus to the next visible item
- `ArrowUp` — Moves focus to the previous visible item
- `ArrowRight` — Expands a collapsed branch, or moves to the first child when already expanded
- `ArrowLeft` — Collapses an expanded branch, or moves to the parent item
- `Home` — Moves focus to the first visible item
- `End` — Moves focus to the last visible item
- `Enter` — Activates the item (selects or toggles expansion depending on mode)
- `Space` — Activates the item (selects or toggles expansion depending on mode)
- `*` — Expands all sibling branches of the focused item

### Selectors & testing

Roles and ARIA states are set on `ElementInternals` (the accessibility-tree source) **and** mirrored to DOM attributes, so both `[role]` and `[aria-*]` selectors keep matching in CSS, `querySelector`, and Cypress/Playwright. Selection state is additionally exposed as the reflected `selected`/`expanded`/`disabled`/`indeterminate` boolean attributes (the component's own API).

```js
document.querySelectorAll('[role="treeitem"]'); // ✅ role is mirrored to an attribute
document.querySelectorAll('[aria-selected="true"]'); // ✅ ARIA state is mirrored too
tree.querySelectorAll('l-tree-item[selected]'); // ✅ reflected boolean attribute
screen.getByRole('treeitem', { selected: true, expanded: true }); // ✅ name + state
```

```css
/* Style by ARIA state or the reflected boolean attribute — both work. */
l-tree-item[aria-selected='true']::part(base) {
  background: var(--l-color-bg-fill-brand-subtle);
}
```

## API reference

### Importing

```js
import 'luxen-ui/tree';
import 'luxen-ui/tree-item';
```

### Attributes & Properties

- **selection**: `TreeSelection` (default: `'single'`) — Selection behaviour:
- `single` (default): at most one item selected via `aria-selected`.
- `multiple`: any number of items selected. Checkboxes are rendered.
- `leaf`: only leaf items can be selected (single). Branches just toggle.
- `none`: purely navigable, no selection state.
- **independent**: `boolean` (default: `false`) — When set with `selection="multiple"`, parent and children selection are decoupled:
toggling a parent does NOT toggle its descendants and vice versa.
Without it, selection cascades both ways and branches may become indeterminate.

### Methods

- **getAllItems({ includeDisabled = true }: unknown)** → `TreeItem[]` — Returns all items in document (flat) order, including nested ones.
- **getSelection()** → `TreeItem[]` — Returns currently selected items.
- **expandAll()** — Expands every item that has children.
- **collapseAll()** — Collapses every item.

### Events

- **selection-change** — Fired when the selected items change. Properties: `selection: TreeItem[]`.

### CSS custom properties

- `--indent-size` (default: `1rem`) — Horizontal indent per depth level.
- `--indent-guide-width` (default: `1px`) — Thickness of the vertical guide line between a parent and its children. Set to `0` to hide guides.
- `--indent-guide-style` (default: `solid`) — Line style of the guide (`solid`, `dashed`, `dotted`, `double`…).
- `--indent-guide-color` — Color of the guide line.
- `--row-height` (default: `1.75rem`) — Minimum row height.
- `--row-padding-inline` (default: `0.25rem`) — Inner inline padding of the row; also drives the content slot left indent and the indent guide column.
- `--chevron-size` (default: `1.125rem`) — Size of the expand/collapse chevron box.
- `--item-gap` (default: `0.375rem`) — Horizontal gap between chevron, prefix, label and suffix on the row; also drives the content slot left indent.

See [`<l-tree-item>`](/elements/tree-item) for the per-item API.
