Navigation · astro-ui · TreeNew

TreeJS

Keyboard-navigable file/folder hierarchy with guide lines.

Tree renders a nested role="tree" from a plain items array — no markup authoring, no recursion in your page. Branches toggle aria-expanded, leaves with an href are real links, and one tab stop plus arrow keys give the whole hierarchy a single keyboard entry point. Because the structure is semantic, screen readers announce level and position for free.

Features

  • Data-driven recursion via `Astro.self` — any depth, correct `aria-level`
  • WAI-ARIA tree view keyboard model: arrows, Home/End, typeahead
  • Single tab stop with roving focus; pointer clicks move it too
  • Real links for leaves (`href`), so middle-click and copy-link work
  • Disabled nodes are skipped by keyboard navigation
  • Guide lines, twisties, and optional per-node icons

Usage

---
import { Tree } from "astro-ui";
---

<Tree
  ariaLabel="Project files"
  items={[
    {
      id: "src",
      label: "src",
      icon: "▸",
      expanded: true,
      children: [
        { id: "app", label: "App.astro" },
        { id: "lib", label: "lib", children: [{ id: "cn", label: "cn.ts" }] },
      ],
    },
    { id: "pkg", label: "package.json" },
  ]}
/>

Demos

Project files

  • src
    • App.astro
  • package.json
<Tree
  ariaLabel="Project files"
  items={[
    {
      id: "src",
      label: "src",
      icon: "▸",
      expanded: true,
      children: [
        { id: "app", label: "App.astro" },
        { id: "lib", label: "lib", children: [{ id: "cn", label: "cn.ts" }] },
      ],
    },
    { id: "pkg", label: "package.json" },
  ]}
/>

Deep hierarchy with links

<Tree
  ariaLabel="Documentation"
  selected="button"
  items={[
    {
      id: "components",
      label: "Components",
      expanded: true,
      children: [
        {
          id: "general",
          label: "General",
          expanded: true,
          children: [
            { id: "button", label: "Button", href: "/components/button" },
            { id: "kbd", label: "Kbd", href: "/components/kbd" },
          ],
        },
        {
          id: "overlay",
          label: "Overlay",
          expanded: true,
          children: [
            { id: "menu", label: "Menu", href: "/components/menu" },
            { id: "sheet", label: "Sheet", href: "/components/sheet" },
          ],
        },
      ],
    },
  ]}
/>

Disabled nodes

  • main
    • feat/tree
  • release/1.0 (locked)
<Tree
  ariaLabel="Branches"
  items={[
    { id: "main", label: "main", expanded: true, children: [{ id: "a", label: "feat/tree" }] },
    { id: "locked", label: "release/1.0 (locked)", disabled: true },
  ]}
/>

API

Props

PropTypeDefaultDescription
itemsTreeNode[]—The hierarchy; see the TreeNode shape below.
ariaLabelstring—Accessible name for the tree (required).
selectedstring—Node id exposed as `aria-selected="true"`.
classstring—Merged via cn(); Tailwind classes welcome.
idstring—Element id.
ariaLabelstring—Accessible name.

Accessibility

Implemented accessibility features — semantics, ARIA wiring, and keyboard behavior that ship with the component.

  • role="tree" labelled by ariaLabel (required)
  • role="treeitem" with aria-level and aria-expanded on branches
  • Nested children render in role="group", hidden when collapsed
  • Derived keyboard model: → expand/enter, ← collapse/exit, ↑ ↓ move, Home/End, typeahead
  • Disabled nodes expose aria-disabled and never take focus
  • selected marks the current item with aria-selected="true"

FAQ

What does a TreeNode look like?

`{ id: string; label: string; icon?: string; href?: string; children?: TreeNode[]; expanded?: boolean; disabled?: boolean }`. The presence of the `children` key marks a branch — an empty array renders a branch that expands to nothing.

How do I select nodes?

Tree is a navigation/disclosure widget: it toggles branches and follows links. For selection, listen for clicks on `[data-tree-node]` and render your own selected styling with `aria-selected`.

Why is the tree focusable as a whole?

It uses roving tabindex: exactly one item has `tabindex="0"` and arrow keys move that tab stop. Tabbing in lands on the last-focused node, then Tab leaves the tree — one stop, no keyboard trap.

Changelog

Version history mentioning Tree.

v0.1.0 — 2026-11-11
added
  • Navigation (7): Tabs, Breadcrumb, Pagination, Stepper, Tree, Navbar, Sidebar.
  • Native-first interactive behavior: Dialog, AlertDialog, and Sheet on <dialog>; Accordion and Collapsible on <details>; Tabs, Menu, Tree, and Combobox with WAI-ARIA roles and scoped keyboard scripts.