Skip to main content
Graphite UI

Tree view

A hierarchy a reader opens a level at a time: branches that open and close, and leaves under them. Use it for nested navigation, like this site’s sidebar, or a file tree; when the list is one level deep, a Contained list is enough.

Contract 1.0.0Kit · 4 sets · 50 variantsOpen in Figma

Live preview

Rendered by the component itself from the same generated tokens as the rest of the site.

  • components
    • ui
      • button.tsx
      • tree-view.tsx
    • kit-icon.tsx
  • package.json

Installation

Install it with the shadcn CLI, along with everything it uses. New project? Set it up first, as Quick start shows.

npx shadcn@latest add https://www.graphite-ui.com/r/tree-view.json
import { TreeView, type TreeNode } from '@/components/ui/tree-view'

Anatomy

Rows of one height. Each starts at its level’s indent, then the caret on a branch, the icon, and the node text, 8 apart and 16 clear of the right edge. The selected row takes primary-container and a 4px primary bar at the left.

  • ui
    • button.tsx
    • tag.tsx
  1. NodesRequiredThe tree's content, as data rather than children. A node with children is the kit's Branch node, which opens and closes; one without is a Leaf. A node with an href is a link, so the tree can be a site's navigation.
  2. Node textRequiredThe kit's Node text, Body/3, one line, ending in an ellipsis when it runs out of room.
  3. IconOptionalThe kit's Icon axis. A Branch draws the folder, a Leaf the document, 8 after the caret; on by default, as the kit's Tree view is.

Variants

Size sets the row’s height, 32 or 24, with the text at Body/3 in both. Icons adds the kit’s folder and document and widens the indent step from 16 to 24, as the kit’s spacers do.

Size: Small
  • ui
    • button.tsx
    • tag.tsx
Size: Extra small
  • ui
    • button.tsx
    • tag.tsx
Icon: True
  • ui
    • button.tsx
    • tag.tsx
Icon: False
  • ui
    • button.tsx
    • tag.tsx

States

Hover climbs one rung of the elevation ladder and pressing two, and the text and glyphs step from on-surface-variant to on-surface. Focus is a ring inside the row. A selected row keeps its fill on hover: the kit’s Selected + Hover step is one the engine does not generate.

Enabled
  • ui
    • button.tsx
    • tag.tsx
Hover
  • ui
    • button.tsx
    • tag.tsx
Active
  • ui
    • button.tsx
    • tag.tsx
Focus
  • ui
    • button.tsx
    • tag.tsx
Selected
  • ui
    • button.tsx
    • tag.tsx
Disabled
  • ui
    • button.tsx
    • tag.tsx

API reference

Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.

Tree view props
PropTypeDefaultNotes
label——Required. The tree's accessible name; a tree with no name is a list of words.
nodes——{ id, label, href?, disabled?, children? }[], any depth. The kit draws four levels; deeper levels keep the same step.
size'sm' | 'xs'—The kit's Size, Small (32) and Extra small (24). Small by default.
iconsbooleantrueThe kit's Icon axis on Tree view and on every node. Also sets the indent step, 24 with icons and 16 without, as the kit's spacers do.
selected——The id of the selected node, the kit's Selected. A site's navigation passes the current page. Its ancestors open.
onSelect——Called with a node's id when it is activated. A link node also navigates, natively.
defaultExpanded——Branch ids open at first. The reader opens and closes them from there.

Design tokens

Every swatch is live. Change the source color in the header and this table repaints, because it reads the same roles the component does.

  • elevationHover (elevation-02, the kit's layer-hover-01) and active (elevation-03, layer-active-01). The row at rest has no fill; see Kit parity.
  • primaryThe selected row's 4px inset bar at the left edge (the kit's interactive); the focus ring, 2px inside the row, through the family's focus step; and disabled text and icons, through its disabled-content step.
  • primary-containerThe selected row (the kit's layer-selected-01).
  • on-surface-variantText, caret and icons at rest (the kit's text-secondary and icon-secondary).
  • on-surfaceText, caret and icons on hover, active and selected (the kit's text-primary and icon-primary).
  • textNode text at Body/3 at both sizes.
  • spacingRow heights, the 16 right padding, the indent steps and the 8 gaps.

Usage

The contract's prohibitions, written as the choices you will actually face.

Do

  • Use it where the content really nests, and let the reader open only what they need.
  • Pass the current page as selected in navigation, so its branch opens and the reader sees where they are.
  • Give each node an href when it is a page, so it behaves as a link: middle-click, copy link and history all work.
  • Name the tree with label. A screen reader announces it before the first node.

Don’t

  • Use it for a flat list. One level is a Contained list, or Navigation Menu for a site header.
  • Put buttons or controls inside a node. The tree takes one tab stop and the arrow keys; a second focus target inside it breaks both.
  • Rely on indent alone to show the levels. The tree announces them, but a reader skimming needs the carets too.
  • Open every branch at once. A tree that shows everything is a long list with extra indentation.

Accessibility

What the component does for you, and what it leaves to you.

Roles
The ARIA tree: a tree named by label, treeitems with aria-level, aria-expanded on a branch and a group for its children. A link node is the treeitem itself, and the current page carries aria-current.
Keyboard
One tab stop. Arrow Down and Up move between the nodes that are showing; Right opens a branch or steps into it; Left closes it or steps out to the parent; Home and End go to the ends; Enter and Space activate; a typed letter jumps to the next node that starts with it.
Focus
A 2px ring inside the row, through primary’s focus step. The tab stop follows the last node focused, else the selected one, else the first.
Selection
The selected node carries aria-selected. Its ancestors open, so it is always reachable.
Disabled
A disabled node dims, carries aria-disabled, and stays reachable by the arrow keys so it is announced, but does not activate.
Contrast
Text at rest is on-surface-variant on whatever surface holds the tree, and on-surface on the hover, press and selected fills; each pair is measured at the theme’s target.

Figma parity

The kit's Tree view page ships 50 variants across 4 sets: Tree view and Branch node item, which are public, and the private Branch and Leaf spacers the rows indent with.

Kit axes and how they map to code
Kit axisValuesCodeHow it maps
NodeBranch · LeafchildrenA node with children is a Branch, with the caret; without, a Leaf.
SizeSmall · Extra smallsizesm and xs: 32 and 24.
IconTrue · FalseiconsThe folder and the document, and the indent step: 24 with icons, 16 without.
OpenFalse · TruedefaultExpanded, then the readercaret-right turns to caret-down. The selected node’s ancestors open.
StateEnabled · Hover · Focus · Active · DisableddisabledHover, Focus and Active are pseudo-classes (governance rule 7): elevation-02 and -03, and the inset ring.
SelectedFalse · Trueselectedprimary-container and a 4px primary bar inside the left edge.
Filllayer-01—No fill at rest. layer-01 is the surface Carbon assumes the tree sits on; transparent reproduces it on whatever holds the tree, rather than drawing a panel on the page.
StateSelected + Hover—The kit fills state/primary-container-hover, which the engine does not generate. A selected row ignores hover, as Data table’s do.
Show Badge indicatorBoolean—Defined on the set, but no layer in any variant uses it. Nothing to build.
LevelsLevel 1 to 4any depthThe kit’s spacers stop at four; the code keeps the same step past them.