Add this component

npx zentauri-ui add tree-view

Accessibility notes

Structure-first markup keeps dense data navigable. Preserve row, heading, and item labels when adapting examples.

Dependency notes

Static imports do not pull framer-motion. Use the animated entry only when you want motion and have framer-motion installed.

Data

Accessible hierarchical trees for developer tools

TreeView renders nested data as an accessible, keyboard-navigable tree. Use it for file explorers, org charts, nested configuration, and any hierarchy where users expand, collapse, and select nodes.

  • workspace
      1. apps
          1. web
          2. docs
      2. packages
          1. components
          2. config
      3. package.json

Examples

TreeView renders nested data with keyboard navigation, selection state, optional indentation guides, and an animated expand/collapse variant.


  • src
      1. ui
          1. tree-view.tsx
          2. scroll-area.tsx
      2. index.ts
  • README.md

  • Components
      1. Overview
      2. TreeView
      3. Legacy table

Tree view variants playground

Pick an appearance, size, guide state, and animation transition to preview the tree view live. Toggle Show output / Show code and the snippet updates to match the selected variant.


  • app
      1. preview
          1. page.tsx
          2. layout.tsx
      2. globals.css
  • components
      1. tree-view.tsx
      2. scroll-area.tsx
      3. draft.tsx
  • README.md

All appearances

Every shipped appearance token at a glance. Click any swatch to load it into the playground above.

Tree View API

Generated from the package prop types and variant definitions.

TreeGroup

TreeGroupProps

State

PropTypeDefault
open*booleannone

Behavior

PropTypeDefault
level*numbernone

Content

PropTypeDefault
children*ReactNodenone

TreeView

TreeViewProps

Variants

PropTypeDefault
appearance
aquabluecardcrimsoncyandefaultelectricemeraldforestghostgradient-bluegradient-green+32
default
size
lgmdsm
md

Behavior

PropTypeDefault
data*TreeNode[]none
defaultExpandedUncontrolled set of expanded node ids.string[] | undefinednone
defaultSelectedUncontrolled selected node id.string | undefinednone
expandedControlled set of expanded node ids.string[] | undefinednone
onExpandedChange((ids: string[]) => void) | undefinednone
onSelect((node: TreeNode) => void) | undefinednone
renderNodeReplace the default label rendering for each node.((args: TreeViewRenderArgs) => ReactNode) | undefinednone
selectedControlled selected node id.string | undefinednone
showGuidesDraw vertical indentation guide lines for nested levels.boolean | undefinednone
Inherited HTML props
PropTypeDefault
classNamestring | undefinednone

TreeViewVariant

TreeViewVariantProps

Behavior

PropTypeDefault
appearance'default' | 'outline' | 'ghost' | 'card' | 'separated' | 'blue' | 'cyan' | 'green' | 'lime' | 'mint' | 'ocean' | 'sapphire' | 'lavender' | 'ruby' | 'red' | 'slate' | 'zinc' | 'stone' | 'royal' | 'electric' | 'forest' | 'sunset' | 'magenta' | 'crimson' | 'aqua' | 'plum' | 'sky' | 'rose' | 'purple' | 'pink' | 'orange' | 'yellow' | 'teal' | 'indigo' | 'emerald' | 'gradient-blue' | 'gradient-green' | 'gradient-red' | 'gradient-yellow' | 'gradient-purple' | 'gradient-teal' | 'gradient-indigo' | 'gradient-pink' | 'gradient-orange' | null | undefinednone
size'md' | 'sm' | 'lg' | null | undefinednone

TreeViewAnimated

TreeViewAnimatedProps

animated

Behavior

PropTypeDefault
appearance'default' | 'outline' | 'ghost' | 'card' | 'separated' | 'blue' | 'cyan' | 'green' | 'lime' | 'mint' | 'ocean' | 'sapphire' | 'lavender' | 'ruby' | 'red' | 'slate' | 'zinc' | 'stone' | 'royal' | 'electric' | 'forest' | 'sunset' | 'magenta' | 'crimson' | 'aqua' | 'plum' | 'sky' | 'rose' | 'purple' | 'pink' | 'orange' | 'yellow' | 'teal' | 'indigo' | 'emerald' | 'gradient-blue' | 'gradient-green' | 'gradient-red' | 'gradient-yellow' | 'gradient-purple' | 'gradient-teal' | 'gradient-indigo' | 'gradient-pink' | 'gradient-orange' | null | undefinednone
data*TreeNode[]none
defaultExpandedUncontrolled set of expanded node ids.string[] | undefinednone
defaultSelectedUncontrolled selected node id.string | undefinednone
expandedControlled set of expanded node ids.string[] | undefinednone
onExpandedChange((ids: string[]) => void) | undefinednone
onSelect((node: TreeNode) => void) | undefinednone
renderNodeReplace the default label rendering for each node.((args: TreeViewRenderArgs) => ReactNode) | undefinednone
selectedControlled selected node id.string | undefinednone
showGuidesDraw vertical indentation guide lines for nested levels.boolean | undefinednone
size'md' | 'sm' | 'lg' | null | undefinednone
transitionVariantTreeViewTransition | undefinednone
Inherited HTML props
PropTypeDefault
classNamestring | undefinednone
CSS variable overrides

TreeView CSS variables

Override these TreeView variables on :root, a theme selector, or a component wrapper.

431 variables

Pattern: --zui-<component>-<slot?>-<variant?>-<property>-<state?>-dark?

:root {
  --zui-tree-view-fg: oklch(20.8% 0.042 265.755);
  --zui-tree-view-default-border: #0000001a;
  --zui-tree-view-outline-border: #00000026;
  --zui-tree-view-card-border: #0000001a;
  --zui-tree-view-card-bg: #0000000d;
  --zui-tree-view-separated-border: #0000001a;
  --zui-tree-view-separated-bg: oklch(98.4% 0.003 247.858 / 0.4);
  --zui-tree-view-sky-border: oklch(44.3% 0.11 240.79);
  --zui-tree-view-rose-border: oklch(45.5% 0.188 13.697);
  --zui-tree-view-purple-border: oklch(43.8% 0.218 303.724);
  --zui-tree-view-pink-border: oklch(45.9% 0.187 3.815);
  --zui-tree-view-orange-border: oklch(47% 0.157 37.304);
  --zui-tree-view-yellow-border: oklch(47.6% 0.114 61.907);
  --zui-tree-view-teal-border: oklch(43.7% 0.078 188.216);
  --zui-tree-view-indigo-border: oklch(39.8% 0.195 277.366);
  --zui-tree-view-emerald-border: oklch(43.2% 0.095 166.913);
  --zui-tree-view-gradient-blue-border: oklch(42.4% 0.199 265.638);
  --zui-tree-view-gradient-green-border: oklch(44.8% 0.119 151.328);
  --zui-tree-view-gradient-red-border: oklch(44.4% 0.177 26.899);
  --zui-tree-view-gradient-yellow-border: oklch(47.6% 0.114 61.907);
  --zui-tree-view-gradient-purple-border: oklch(43.8% 0.218 303.724);
  --zui-tree-view-gradient-teal-border: oklch(43.7% 0.078 188.216);
  --zui-tree-view-gradient-indigo-border: oklch(39.8% 0.195 277.366);
  --zui-tree-view-gradient-pink-border: oklch(45.9% 0.187 3.815);
  --zui-tree-view-gradient-orange-border: oklch(47% 0.157 37.304);
  --zui-tree-view-blue-border: #2563eb;
  --zui-tree-view-blue-bg: transparent;
  --zui-tree-view-blue-fg: #0f172a;
  --zui-tree-view-cyan-border: #0891b2;
  --zui-tree-view-cyan-bg: transparent;
  --zui-tree-view-cyan-fg: #0f172a;
  --zui-tree-view-green-border: #16a34a;
  --zui-tree-view-green-bg: transparent;
  --zui-tree-view-green-fg: #0f172a;
  --zui-tree-view-lime-border: #65a30d;
  --zui-tree-view-lime-bg: transparent;
  --zui-tree-view-lime-fg: #0f172a;
  --zui-tree-view-mint-border: #10b981;
  --zui-tree-view-mint-bg: transparent;
  --zui-tree-view-mint-fg: #0f172a;
  --zui-tree-view-ocean-border: #0284c7;
  --zui-tree-view-ocean-bg: transparent;
  --zui-tree-view-ocean-fg: #0f172a;
  --zui-tree-view-sapphire-border: #1d4ed8;
  --zui-tree-view-sapphire-bg: transparent;
  --zui-tree-view-sapphire-fg: #0f172a;
  --zui-tree-view-lavender-border: #8b5cf6;
  --zui-tree-view-lavender-bg: transparent;
  --zui-tree-view-lavender-fg: #0f172a;
  --zui-tree-view-ruby-border: #be123c;
  --zui-tree-view-ruby-bg: transparent;
  --zui-tree-view-ruby-fg: #0f172a;
  --zui-tree-view-red-border: #dc2626;
  --zui-tree-view-red-bg: transparent;
  --zui-tree-view-red-fg: #0f172a;
  --zui-tree-view-slate-border: #475569;
  --zui-tree-view-slate-bg: transparent;
  --zui-tree-view-slate-fg: #0f172a;
  --zui-tree-view-zinc-border: #52525b;
  --zui-tree-view-zinc-bg: transparent;
  --zui-tree-view-zinc-fg: #0f172a;
  --zui-tree-view-stone-border: #57534e;
  --zui-tree-view-stone-bg: transparent;
  --zui-tree-view-stone-fg: #0f172a;
  --zui-tree-view-royal-border: #4338ca;
  --zui-tree-view-royal-bg: transparent;
  --zui-tree-view-royal-fg: #0f172a;
  --zui-tree-view-electric-border: #0ea5e9;
  --zui-tree-view-electric-bg: transparent;
  --zui-tree-view-electric-fg: #0f172a;
  --zui-tree-view-forest-border: #166534;
  --zui-tree-view-forest-bg: transparent;
  --zui-tree-view-forest-fg: #0f172a;
  --zui-tree-view-sunset-border: #ea580c;
  --zui-tree-view-sunset-bg: transparent;
  --zui-tree-view-sunset-fg: #0f172a;
  --zui-tree-view-magenta-border: #c026d3;
  --zui-tree-view-magenta-bg: transparent;
  --zui-tree-view-magenta-fg: #0f172a;
  --zui-tree-view-crimson-border: #b91c1c;
  --zui-tree-view-crimson-bg: transparent;
  --zui-tree-view-crimson-fg: #0f172a;
  --zui-tree-view-aqua-border: #0f766e;
  --zui-tree-view-aqua-bg: transparent;
  --zui-tree-view-aqua-fg: #0f172a;
  --zui-tree-view-plum-border: #7e22ce;
  --zui-tree-view-plum-bg: transparent;
  --zui-tree-view-plum-fg: #0f172a;
  --zui-tree-view-item-hover: #0000000d;
  --zui-tree-view-ring-focus: #0000004d;
  --zui-tree-view-selected: #0000000f;
  --zui-tree-view-selected-fg: oklch(20.8% 0.042 265.755);
  --zui-tree-view-sky-selected: oklch(95.1% 0.026 236.824);
  --zui-tree-view-sky-selected-fg: oklch(44.3% 0.11 240.79);
  --zui-tree-view-rose-selected: oklch(94.1% 0.03 12.58);
  --zui-tree-view-rose-selected-fg: oklch(45.5% 0.188 13.697);
  --zui-tree-view-purple-selected: oklch(94.6% 0.033 307.174);
  --zui-tree-view-purple-selected-fg: oklch(43.8% 0.218 303.724);
  --zui-tree-view-pink-selected: oklch(94.8% 0.028 342.258);
  --zui-tree-view-pink-selected-fg: oklch(45.9% 0.187 3.815);
  --zui-tree-view-orange-selected: oklch(95.4% 0.038 75.164);
  --zui-tree-view-orange-selected-fg: oklch(47% 0.157 37.304);
  --zui-tree-view-yellow-selected: oklch(97.3% 0.071 103.193);
  --zui-tree-view-yellow-selected-fg: oklch(47.6% 0.114 61.907);
  --zui-tree-view-teal-selected: oklch(95.3% 0.051 180.801);
  --zui-tree-view-teal-selected-fg: oklch(43.7% 0.078 188.216);
  --zui-tree-view-indigo-selected: oklch(93% 0.034 272.788);
  --zui-tree-view-indigo-selected-fg: oklch(39.8% 0.195 277.366);
  --zui-tree-view-emerald-selected: oklch(95% 0.052 163.051);
  --zui-tree-view-emerald-selected-fg: oklch(43.2% 0.095 166.913);
  --zui-tree-view-gradient-blue-from: oklch(80.9% 0.105 251.813);
  --zui-tree-view-gradient-blue-to: oklch(82.7% 0.119 306.383);
  --zui-tree-view-gradient-fg: #ffffff;
  --zui-tree-view-gradient-green-from: oklch(87.1% 0.15 154.449);
  --zui-tree-view-gradient-green-to: oklch(89.7% 0.196 126.665);
  --zui-tree-view-gradient-red-from: oklch(80.8% 0.114 19.571);
  --zui-tree-view-gradient-red-to: oklch(82.3% 0.12 346.018);
  --zui-tree-view-gradient-yellow-from: oklch(90.5% 0.182 98.111);
  --zui-tree-view-gradient-yellow-to: oklch(83.7% 0.128 66.29);
  --zui-tree-view-gradient-purple-from: oklch(82.7% 0.119 306.383);
  --zui-tree-view-gradient-purple-to: oklch(82.3% 0.12 346.018);
  --zui-tree-view-gradient-teal-from: oklch(85.5% 0.138 181.071);
  --zui-tree-view-gradient-teal-to: oklch(86.5% 0.127 207.078);
  --zui-tree-view-gradient-indigo-from: oklch(78.5% 0.115 274.713);
  --zui-tree-view-gradient-indigo-to: oklch(82.7% 0.119 306.383);
  --zui-tree-view-gradient-pink-from: oklch(82.3% 0.12 346.018);
  --zui-tree-view-gradient-pink-to: oklch(81% 0.117 11.638);
  --zui-tree-view-gradient-orange-from: oklch(83.7% 0.128 66.29);
  --zui-tree-view-gradient-orange-to: oklch(80.8% 0.114 19.571);
  --zui-tree-view-item-blue-bg-hover: #2563eb18;
  --zui-tree-view-item-blue-fg-hover: #2563eb;
  --zui-tree-view-item-blue-bg: #2563eb14;
  --zui-tree-view-item-blue-fg: #2563eb;
  --zui-tree-view-item-cyan-bg-hover: #0891b218;
  --zui-tree-view-item-cyan-fg-hover: #0891b2;
  --zui-tree-view-item-cyan-bg: #0891b214;
  --zui-tree-view-item-cyan-fg: #0891b2;
  --zui-tree-view-item-green-bg-hover: #16a34a18;
  --zui-tree-view-item-green-fg-hover: #16a34a;
  --zui-tree-view-item-green-bg: #16a34a14;
  --zui-tree-view-item-green-fg: #16a34a;
  --zui-tree-view-item-lime-bg-hover: #65a30d18;
  --zui-tree-view-item-lime-fg-hover: #65a30d;
  --zui-tree-view-item-lime-bg: #65a30d14;
  --zui-tree-view-item-lime-fg: #65a30d;
  --zui-tree-view-item-mint-bg-hover: #10b98118;
  --zui-tree-view-item-mint-fg-hover: #10b981;
  --zui-tree-view-item-mint-bg: #10b98114;
  --zui-tree-view-item-mint-fg: #10b981;
  --zui-tree-view-item-ocean-bg-hover: #0284c718;
  --zui-tree-view-item-ocean-fg-hover: #0284c7;
  --zui-tree-view-item-ocean-bg: #0284c714;
  --zui-tree-view-item-ocean-fg: #0284c7;
  --zui-tree-view-item-sapphire-bg-hover: #1d4ed818;
  --zui-tree-view-item-sapphire-fg-hover: #1d4ed8;
  --zui-tree-view-item-sapphire-bg: #1d4ed814;
  --zui-tree-view-item-sapphire-fg: #1d4ed8;
  --zui-tree-view-item-lavender-bg-hover: #8b5cf618;
  --zui-tree-view-item-lavender-fg-hover: #8b5cf6;
  --zui-tree-view-item-lavender-bg: #8b5cf614;
  --zui-tree-view-item-lavender-fg: #8b5cf6;
  --zui-tree-view-item-ruby-bg-hover: #be123c18;
  --zui-tree-view-item-ruby-fg-hover: #be123c;
  --zui-tree-view-item-ruby-bg: #be123c14;
  --zui-tree-view-item-ruby-fg: #be123c;
  --zui-tree-view-item-red-bg-hover: #dc262618;
  --zui-tree-view-item-red-fg-hover: #dc2626;
  --zui-tree-view-item-red-bg: #dc262614;
  --zui-tree-view-item-red-fg: #dc2626;
  --zui-tree-view-item-slate-bg-hover: #47556918;
  --zui-tree-view-item-slate-fg-hover: #475569;
  --zui-tree-view-item-slate-bg: #47556914;
  --zui-tree-view-item-slate-fg: #475569;
  --zui-tree-view-item-zinc-bg-hover: #52525b18;
  --zui-tree-view-item-zinc-fg-hover: #52525b;
  --zui-tree-view-item-zinc-bg: #52525b14;
  --zui-tree-view-item-zinc-fg: #52525b;
  --zui-tree-view-item-stone-bg-hover: #57534e18;
  --zui-tree-view-item-stone-fg-hover: #57534e;
  --zui-tree-view-item-stone-bg: #57534e14;
  --zui-tree-view-item-stone-fg: #57534e;
  --zui-tree-view-item-royal-bg-hover: #4338ca18;
  --zui-tree-view-item-royal-fg-hover: #4338ca;
  --zui-tree-view-item-royal-bg: #4338ca14;
  --zui-tree-view-item-royal-fg: #4338ca;
  --zui-tree-view-item-electric-bg-hover: #0ea5e918;
  --zui-tree-view-item-electric-fg-hover: #0ea5e9;
  --zui-tree-view-item-electric-bg: #0ea5e914;
  --zui-tree-view-item-electric-fg: #0ea5e9;
  --zui-tree-view-item-forest-bg-hover: #16653418;
  --zui-tree-view-item-forest-fg-hover: #166534;
  --zui-tree-view-item-forest-bg: #16653414;
  --zui-tree-view-item-forest-fg: #166534;
  --zui-tree-view-item-sunset-bg-hover: #ea580c18;
  --zui-tree-view-item-sunset-fg-hover: #ea580c;
  --zui-tree-view-item-sunset-bg: #ea580c14;
  --zui-tree-view-item-sunset-fg: #ea580c;
  --zui-tree-view-item-magenta-bg-hover: #c026d318;
  --zui-tree-view-item-magenta-fg-hover: #c026d3;
  --zui-tree-view-item-magenta-bg: #c026d314;
  --zui-tree-view-item-magenta-fg: #c026d3;
  --zui-tree-view-item-crimson-bg-hover: #b91c1c18;
  --zui-tree-view-item-crimson-fg-hover: #b91c1c;
  --zui-tree-view-item-crimson-bg: #b91c1c14;
  --zui-tree-view-item-crimson-fg: #b91c1c;
  --zui-tree-view-item-aqua-bg-hover: #0f766e18;
  --zui-tree-view-item-aqua-fg-hover: #0f766e;
  --zui-tree-view-item-aqua-bg: #0f766e14;
  --zui-tree-view-item-aqua-fg: #0f766e;
  --zui-tree-view-item-plum-bg-hover: #7e22ce18;
  --zui-tree-view-item-plum-fg-hover: #7e22ce;
  --zui-tree-view-item-plum-bg: #7e22ce14;
  --zui-tree-view-item-plum-fg: #7e22ce;
  --zui-tree-view-chevron: oklch(55.5% 0.041 257.417);
  --zui-tree-view-icon: oklch(55.5% 0.041 257.417);
  --zui-tree-view-guide: #0000001a;
}

/* Dark theme variables follow the same names with -dark appended. */
.dark {
  --zui-tree-view-fg-dark: oklch(92.9% 0.013 255.508);
  --zui-tree-view-default-border-dark: #ffffff1a;
}

What it does

TreeView turns an array of nested nodes into an accessible tree with expandable branches and selectable leaves.

It manages expansion and selection state (controlled or uncontrolled), roving tabindex focus, and full keyboard navigation, while leaving node rendering open through renderNode.

Composition and API

Pass a data array of nodes, each with an id, label, optional icon, optional disabled flag, and optional children. Use defaultExpanded or expanded for expansion, defaultSelected or selected for selection, and onSelect and onExpandedChange for events. Variants such as appearance, size, and showGuides theme the container and indentation.

Common use cases

Use TreeView for file and folder explorers, nested navigation, settings categories, taxonomy editors, and org charts. The renderNode prop lets each row carry icons, badges, counts, or inline actions while the component keeps accessibility and keyboard behavior consistent.

Accessibility

The container uses role=tree and each node uses role=treeitem with aria-expanded, aria-selected, aria-level, and aria-disabled. A roving tabindex keeps one node in the tab order, and arrow keys, Home, End, Enter, and Space follow the WAI-ARIA tree interaction pattern.

Next.js integration notes

TreeView ships as a client-safe package entry because it owns focus and keyboard state. Render it inside App Router preview pages while keeping SEO content server-rendered, and reach for the ui/tree-view/animated entry only when you want motion on expand and collapse.

FAQ

Is TreeView keyboard accessible?

Yes. TreeView implements the WAI-ARIA tree pattern: arrow keys move between visible nodes, ArrowRight expands or steps into children, ArrowLeft collapses or steps to the parent, Home and End jump to the first and last node, and Enter or Space selects.

How do I control which nodes are expanded?

Pass defaultExpanded for uncontrolled state, or expanded plus onExpandedChange for controlled state. Selection works the same way with defaultSelected or selected plus onSelect.

Does the animated variant require Framer Motion?

Only the animated entry imports framer-motion. Import TreeView from ui/tree-view for the static version, or TreeViewAnimated from ui/tree-view/animated for spring-based expand and collapse.