Tree
A hierarchical, keyboard-navigable list of expandable nodes built from a data array.
Live demo
Tree renders a nested hierarchy from an items array. Each node carries a label, an optional value, href, and children, plus flags for expanded, selected, and disabled.
It builds the WAI-ARIA tree pattern: a role="tree" with nested role="group" levels and role="treeitem" nodes carrying aria-level, aria-expanded, and aria-selected, with a single roving tab stop so the whole tree is one Tab stop and the arrow keys walk it. A twisty rotates on expand. A node with an href renders its row as a link whether or not it has children, so a branch can be both navigable and a group; the row navigates while the twisty (and Left/Right) work the children. A branch without an href is a pure container that toggles on click. A node also carries per-row trailing content: one or more badge pills (a count plus a toned status pill after the label) and actions (hover-revealed row buttons that fire a tree-action event, each optionally disabled in place), the file-tree-with-inline-controls shape. Being data-driven keeps it robust across the bindings and a natural fit for a file or navigation tree. Three sizes (sm, md, lg) scale the row density.
When to use
How this component composes with the rest of the set.
Props
4 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
Sizes
sm
Compact rows.
md
Default.
lg
Roomy rows.
States
selected
The chosen node: its row takes the accent background and text. When the theme's --selection-cue resolves to marker (a high-contrast or redundant-cues algorithm), a non-color check glyph is added so selection never rests on color alone.
row-hover
Pointer over a row: the hover tint.
expanded
An open parent: the twisty rotates and the child group shows.
focus-visible
Keyboard focus on a node: an inset token ring on its row.
disabled
A locked node: muted and non-interactive.
Anatomy
The named parts that make up the component, with their selectors.
tree
The role="tree" root list holding the top-level nodes.
row
A node's clickable row, indented by its level; a <div>, or an <a> when the node has an href.
twisty
The disclosure caret on parent nodes; rotates 90° when the node is expanded, hidden on leaves.
group
A nested role="group" list of child nodes; hidden when its parent is collapsed.
trailing
Per-row content after the label: one or more badge pills (a count plus a toned status, always shown) and hover-revealed actions buttons. Reachable at ::part(badge) and ::part(row-action).
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xtyle-default register
(derive(xtyleDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent-2-text
--accent-3-text
--accent-4-text
--accent-bg
--accent-text
--black-text
--blue-text
--border-normal
--border-thick
--brown-text
--cyan-text
--danger-text
--duration-fast
--ease-standard
--fg-0
--fg-1
--fg-2
--fg-3
--fg-disabled
--font-sans
--gray-text
--green-text
--info-text
--neutral-text
--orange-text
--pink-text
--purple-text
--radius-sm
--red-text
--ring
--selection-cue
--space-0
--space-1
--space-2
--space-4
--state-hover
--success-text
--text-body
--text-sm
--text-xs
--warn-text
--weight-medium
--white-text
--yellow-text
Accessibility
Code
A documentation tree
An expanded Guides branch with a selected page and links, beside a collapsed Reference branch.
<xtyle-tree label="Documentation"></xtyle-tree>
<script>
document.querySelector("xtyle-tree").items = [
{
label: "Guides",
expanded: true,
children: [
{ label: "Getting started", href: "/guides/start", selected: true },
{ label: "Theming", href: "/guides/theming" },
],
},
{
label: "Reference",
children: [{ label: "Engine", href: "/reference/engine" }],
},
];
</script>
<script lang="ts">
import { Tree } from "@xtyle/svelte";
const items = [
{
label: "Guides",
expanded: true,
children: [
{ label: "Getting started", href: "/guides/start", selected: true },
{ label: "Theming", href: "/guides/theming" },
],
},
{
label: "Reference",
children: [
{ label: "Engine", value: "engine", actions: [{ id: "rename", label: "Rename", icon: "✎" }] },
],
},
];
</script>
<Tree
label="Documentation"
{items}
onselect={(e) => console.log("select", e.detail.value)}
ontreeaction={(e) => console.log(e.detail.action, "on", e.detail.value)}
/>
---
import { Tree } from "@xtyle/astro";
const items = [
{
label: "Guides",
expanded: true,
children: [
{ label: "Getting started", href: "/guides/start", selected: true },
{ label: "Theming", href: "/guides/theming" },
],
},
{ label: "Reference", children: [{ label: "Engine", href: "/reference/engine" }] },
];
---
<Tree label="Documentation" items={items} />
Badges and row actions
A binder tree where each row carries a trailing badge (a count) and a hover cluster of action buttons, all firing tree-action.
<xtyle-tree label="Binder"></xtyle-tree>
<script>
const tree = document.querySelector("xtyle-tree");
tree.items = [
{
label: "Chapter 1", value: "ch1", expanded: true,
// a word count plus a toned drift pill: two trailing badges, each its own color
badge: ["1,204", { text: "3", tone: "warn" }],
children: [
{ label: "Opening", value: "ch1-1", badge: "512", actions: [
{ id: "up", label: "Move up", icon: "↑", disabled: true },
{ id: "rename", label: "Rename", icon: "✎" },
{ id: "delete", label: "Delete", icon: "✕" },
] },
{ label: "Rising action", value: "ch1-2", actions: [{ id: "rename", label: "Rename", icon: "✎" }] },
],
},
{ label: "Chapter 2", value: "ch2", badge: "812" },
];
// Badges show always; actions reveal on hover and fire tree-action (a disabled action stays greyed).
tree.addEventListener("tree-action", (e) => console.log(e.detail.action, "on", e.detail.value));
</script>