Skip to main content

Tree

Info:Navigation html svelte astro Success: coverage 22/22

A hierarchical, keyboard-navigable list of expandable nodes built from a data array.

Live demo

live · @xoji/astro

Tree

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. 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.

Feed it a value per node and listen for the select event to drive a detail pane or route.
Give leaf nodes an href to render them as links: the natural shape for a docs or file-navigation sidebar.
For a flat set of collapsible sections rather than a hierarchy, reach for Accordion instead.

Props

4 props, straight from the manifest.

PropTypeDefaultBindingsDescription
items TreeNode[]
html svelte astro
The node hierarchy. Each `TreeNode` has a `label` and optional `value`, `href`, `children`, `expanded`, `selected`, and `disabled`. Passed as a property in the bindings (serialized to JSON for the element).
size Size
sm md lg
md
html svelte astro
Row density: `sm`, `md`, or `lg`.
label string
html svelte astro
Accessible name for the tree, applied as `aria-label`.
labelledby string
html svelte astro
Id of an external element naming the tree; takes precedence over `label`.

Appearance

Sizes

sm

.xoji-tree--sm

Compact rows.

md

default
.xoji-tree

Default.

lg

.xoji-tree--lg

Roomy rows.

States

selected

.xoji-tree__item[aria-selected="true"] > .xoji-tree__row

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

.xoji-tree__row:hover

Pointer over a row: the hover tint.

expanded

.xoji-tree__item[aria-expanded="true"]

An open parent: the twisty rotates and the child group shows.

focus-visible

.xoji-tree__item:focus-visible > .xoji-tree__row

Keyboard focus on a node: an inset token ring on its row.

disabled

.xoji-tree__item[aria-disabled="true"] > .xoji-tree__row

A locked node: muted and non-interactive.

Anatomy

The named parts that make up the component, with their selectors.

tree

.xoji-tree

The role="tree" root list holding the top-level nodes.

--font-sans --text-sm --fg-1

row

.xoji-tree__row

A node's clickable row, indented by its level; a <div>, or an <a> when the node has an href.

--space-1 --space-2 --space-4 --radius-sm --fg-0 --fg-2 --state-hover --accent-bg --accent-text --weight-medium --border-normal --border-thick --ring --fg-disabled --duration-fast --ease-standard

twisty

.xoji-tree__twisty

The disclosure caret on parent nodes; rotates 90° when the node is expanded, hidden on leaves.

--fg-3 --duration-fast --ease-standard

group

.xoji-tree__group

A nested role="group" list of child nodes; hidden when its parent is collapsed.

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 22/22 consumed tokens produced default register: 276 tokens

Live coverage check against the xoji-default register (derive(xojiDefault, { anchors })coverComponent(manifest, register)). Every token this component consumes must be a key the algorithm produces.

--accent-bg --accent-text --border-normal --border-thick --duration-fast --ease-standard --fg-0 --fg-1 --fg-2 --fg-3 --fg-disabled --font-sans --radius-sm --ring --selection-cue --space-1 --space-2 --space-4 --state-hover --text-body --text-sm --weight-medium

Accessibility

Builds the WAI-ARIA tree pattern: a role="tree" containing role="treeitem" nodes and nested role="group" levels, each item carrying aria-level, aria-selected, and (on parents) aria-expanded.
A single roving tab stop makes the whole tree one Tab stop; Up/Down move between visible nodes, Right expands or steps into a node, Left collapses or steps out to the parent, and Home/End jump to the first and last.
Enter or Space activates a node: selecting it, toggling a parent, or following a leaf's link.
A node marked disabled is announced and cannot be selected or toggled.
Focus shows an inset token ring on the node's row plus a transparent outline the forced-colors base rule promotes to a real system outline.
Selection carries a non-color channel on demand: when the theme sets --selection-cue: marker, the selected row gains a check glyph alongside the color, satisfying WCAG 1.4.1 (color is not the only differentiator). The algorithm decides. High-contrast emits marker by default, and any algorithm can opt in via the cues knob.

Code

A documentation tree

An expanded Guides branch with a selected page and links, beside a collapsed Reference branch.

<xoji-tree label="Documentation"></xoji-tree>

<script>
	document.querySelector("xoji-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 "@xoji/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", href: "/reference/engine" }] },
	];
</script>

<Tree label="Documentation" {items} onselect={(e) => console.log(e.detail.value)} />
---
import { Tree } from "@xoji/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} />