v1.0

Tree View

Hierarchical list with connector guides, expand and collapse, cascading checkbox selection and drag and drop reordering.

Pro
apps2
docs
app
page.tsx
layout.tsx
package.json
package.json
README.md
turbo.json

Description

TreeView renders nested data as a keyboard-accessible tree. It is built on the React Aria Tree collection, so rows report treeitem semantics with the right level and expanded state, arrow keys walk and open branches, and typeahead jumps to a row by name.

The compound API mirrors the shape of your data: a TreeViewItem holds one TreeViewItemRow for the visible row, and nested TreeViewItem children for its subtree. Depth, indentation and the connector lines are derived from that nesting, so there is nothing to keep in sync by hand.

Use it for file explorers, folder pickers, permission trees, category browsers and nested navigation. For a flat list of options use Select or Combobox, and for a single expandable section use Accordion.

TreeView is a Create UI Pro component. Install it with a Pro seat.

Installation

pnpm dlx @create-ui/cli add tree-view

Anatomy

<TreeView>
  <TreeViewItem id="parent">
    <TreeViewItemRow>Parent</TreeViewItemRow>
 
    <TreeViewItem id="child">
      <TreeViewItemRow>Child</TreeViewItemRow>
    </TreeViewItem>
  </TreeViewItem>
</TreeView>

Usage

import {
  TreeView,
  TreeViewItem,
  TreeViewItemRow,
} from "@/components/ui/tree-view"
<TreeView aria-label="Project files" defaultExpandedKeys={["apps"]}>
  <TreeViewItem id="apps" textValue="apps">
    <TreeViewItemRow>apps</TreeViewItemRow>
 
    <TreeViewItem id="web" textValue="web">
      <TreeViewItemRow>web</TreeViewItemRow>
    </TreeViewItem>
  </TreeViewItem>
</TreeView>

Every TreeViewItem needs a stable id. Pass textValue as well so typeahead, drag previews and screen readers get the plain-text label.

Examples

Every example is a Pro preview (marked with a Pro badge).

Sizes

size sets the row density. sm gives a 36px row with a 44px indentation step, xs gives a 24px row with a 28px step. It cascades from the root to every part.

Pro
src
components
button.tsx
index.ts
src
components
button.tsx
index.ts

Appearance

solid fills the row with bg-weak on hover and selection. ghost leaves the background alone and only lifts the label to text-strongest, which suits trees that sit inside an already busy surface.

Pro
Guides
Overview
Installation
Guides
Overview
Installation

Width

fill stretches the row to the container so the highlight spans the full width. hug shrinks it to the label, so the highlight wraps only the content.

Pro
Clothing
Outerwear
Knitwear

Selection

With selectionMode="multiple" each row gets a checkbox. Selection cascades by default: checking a parent checks its whole subtree, and a partly checked parent renders the indeterminate state.

Pro
Workspace
Members
Billing
Reports
Analytics
Exports

2 selected. Checking a parent checks its whole subtree; a partly checked parent shows the indeterminate state.

Set selectionCascade={false} to keep rows independent, and showCheckbox to force the checkbox on or off regardless of the selection mode.

Icons

Pass icon to put a glyph in the leading slot. There is a single leading slot, so an icon replaces the checkbox on that row, which is how a file tree marks its leaves.

Pro
Media
Photos
sunset.jpg
studio.png
intro.mp4
theme.mp3

Trailing content

trailing takes a badge, a counter or a spinner. The slot keeps the row height and grows sideways as needed. For a loading row the design uses <Spinner variant="neutral-soft" cap="sharp" />, sized sm on an sm tree and xs on an xs one, so the arc reads as a neutral detail rather than a primary accent.

Pro
Productionlive
eu-west-112
Staging
preview

Without guides

guides={false} keeps the indentation but drops the connector lines, which reads better for shallow outlines and navigation trees.

Pro
Getting started
Installation
Theming
Components
Button
Tree View

Drag and drop

dragAndDrop turns on reordering and dropping rows into other rows. onMove reports what happened and leaves the data update to you, so the tree stays a controlled view of your own state. isDropTargetDisabled blocks on drops for rows that cannot contain children.

Pro
Design
tokens.json
icons.svg
Engineering
rfc-001.md
roadmap.md

Accessibility

TreeView inherits React Aria's tree interaction model. Rows form a single tab stop, arrow keys walk the visible rows and open or close branches, and typeahead matches against textValue.

KeyDescription
ArrowDownMoves focus to the next visible row.
ArrowUpMoves focus to the previous visible row.
ArrowRightExpands the focused row, then moves into its first child.
ArrowLeftCollapses the focused row, then moves to its parent.
Home / EndMoves focus to the first or last visible row.
SpaceToggles selection of the focused row.
A to ZTypeahead: jumps to the next row whose textValue starts with it.
TabMoves focus into the focusable controls inside the focused row.

ARIA notes:

  • The root renders role="treegrid" and each row renders role="row" with aria-level, aria-posinset, aria-setsize, aria-expanded and aria-selected.
  • Give the root an aria-label or aria-labelledby; without one the tree is unnamed.
  • textValue is what assistive technology announces, so set it whenever the row's children are not plain text.
  • The connector guides are decorative and marked aria-hidden.
  • The chevron is excluded from the tab order and labelled "expand" or "collapse" by React Aria, so keyboard users toggle a branch with ArrowRight and ArrowLeft rather than tabbing to it.
  • For the full focus, expansion, and drag interaction model, see the React Aria Tree docs.

Styling

Tailwind override: pass className to the root to size or scroll the tree, and to TreeViewItemRow to restyle a single row's wrapper.

Data slots and attributes: the component sets these for CSS targeting:

  • data-slot="tree-view" on the root, with data-size, data-appearance and data-width.
  • data-slot="tree-view-item" on every row, with React Aria's data-level, data-expanded, data-has-child-items, data-selected, data-disabled, data-hovered, data-focus-visible, data-dragging and data-drop-target.
  • data-slot="tree-view-item-content" on the fixed-height box inside the row, the one that folds open and shut.
  • data-slot="tree-view-indent" on each indentation cell, with data-guide ("none", "line", "elbow", "elbow-end").
  • data-slot="tree-view-item-wrapper" on the padded, filled part of the row.
  • data-slot="tree-view-item-drag-handle", -expander, -leading, -icon, -label and -trailing on the row's parts.
  • data-slot="tree-view-drag-preview" on the card that follows the cursor during a drag.
[data-slot="tree-view-item"][data-selected] [data-slot="tree-view-item-label"] {
  /* ... */
}

States: the row wrapper is the only part that changes between states.

StateAttributeSolidGhost
Default-no fill, text-bodyno fill, text-body
Hoverdata-hoveredbg-weaklabel lifts to text-strongest
Focuseddata-focus-visible1px inset primary-base ring, fill unchangedsame ring, label text-strongest
Selecteddata-selectedbg-weak, label text-strongestno fill, label text-strongest
Movingdata-draggingbg-weak, dashed medium frame, 50% opacitysame, label text-strongest
Drop targetdata-drop-targetbg-weak plus the primary-base ringsame

The focus ring is drawn with outline-offset: -1px so it sits inside the wrapper and never changes the row's size.

While a row is being dragged it stays in place under data-dragging, dimmed behind a dashed frame, and its drag handle and connector guides stay at full strength. The thing that follows the cursor is a separate surface, data-slot="tree-view-drag-preview": a lifted white card with a medium border and shadow-neutral-lg. React Aria gives that preview only the dragged row's text, so the card shows the label rather than the row's chevron and slots.

Motion: rows fold open over 200ms and shut over 150ms. Collapsing is held back by exactly one animation, so the closing subtree stays mounted with data-exiting while it plays and the parent carries data-collapsing so its chevron turns back straight away. The list itself has no gap; each row owns its spacing through --tree-view-row-h, which is what lets a folded row measure zero and the rows below it slide instead of jump. Everything is skipped under prefers-reduced-motion.

  • Accordion: use it when you have a handful of independent expandable sections rather than a nested hierarchy.
  • Sidebar: use it for the app shell around a tree, not for the hierarchy itself.
  • Dropdown Menu: use it for a transient list of actions instead of a persistent hierarchy.

API Reference

TreeView

The root. Extends the React Aria Tree props, including expandedKeys, defaultExpandedKeys, onExpandedChange, selectedKeys, defaultSelectedKeys, onSelectionChange, disabledKeys and onAction.

Props

PropTypeDefaultDescription
guidesbooleantrueDraw the indentation connector lines.
showDragHandlebooleandragAndDropShow the drag handle column.
showCheckboxbooleanselectionMode === "multiple"Put a checkbox in the leading slot.
selectionCascadebooleanselectionMode === "multiple"Selecting a row selects its subtree and marks partial parents as indeterminate.
dragAndDropbooleanfalseEnable reordering and dropping rows into other rows.
onMove(event: TreeViewMoveEvent) => void-Fires after a drop with keys, targetKey and position.
isDropTargetDisabled(key: Key) => boolean-Return true for rows that cannot receive an on drop.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-The root TreeViewItem elements.

Variants

VariantOptionsDefaultDescription
size"xs" "sm""sm"Row density. sm is 36px tall, xs is 24px.
appearance"solid" "ghost""solid"solid fills the row, ghost only changes the label colour.
width"fill" "hug""fill"Whether the row stretches to the container or hugs its content.

TreeViewItem

One node. Extends the React Aria TreeItem props, including textValue, hasChildItems, href and isDisabled. Children are one TreeViewItemRow plus any nested TreeViewItem elements.

Props

PropTypeDefaultDescription
idKey-Required. Stable key used for selection, expansion and drag.
textValuestring-Plain-text label for typeahead, drag previews and screen readers.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-One TreeViewItemRow plus any nested TreeViewItem elements.

TreeViewItemRow

The visible row: drag handle, indentation cells and the padded wrapper holding the chevron, leading slot, label and trailing slot.

Props

PropTypeDefaultDescription
iconReact.ReactNode-Leading glyph. Replaces the checkbox on that row.
trailingReact.ReactNode-Trailing content such as a badge, counter or spinner.
classNamestring-Tailwind classes merged onto the row wrapper.
childrenReact.ReactNode-The row label.

TreeViewItemDragHandle

The grab target that starts a drag. TreeViewItemRow renders it when the root has showDragHandle; reach for it directly only when you are building a custom row. Wraps a React Aria Button with slot="drag" and takes no other props.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().

TreeViewItemExpander

The chevron that opens and closes a branch. Wraps a React Aria Button with slot="chevron", so React Aria labels it and keeps it out of the tab order. Takes no other props.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().

TreeViewItemCheckbox

The leading checkbox. Renders the registry Checkbox with variant="neutral", derives its checked value (including "indeterminate") from the root's selection state, and stops its own click from toggling the row twice.

Props

PropTypeDefaultDescription
itemIdKey-Required. The id of the row this checkbox controls.
classNamestring-Tailwind classes merged onto the inner Checkbox via cn().

TreeViewItemIcon / TreeViewItemLabel / TreeViewItemTrailing

The leading glyph, the row label and the trailing slot. All three render a <span> and extend React.ComponentProps<"span">, so any standard span attribute is accepted. TreeViewItemRow renders the icon and trailing slots for you through its icon and trailing props.

Every part reads size and appearance from the root context and exposes neither as a prop of its own.

Types

type TreeViewSize = "xs" | "sm"
type TreeViewAppearance = "solid" | "ghost"
type TreeViewWidth = "fill" | "hug"
type TreeViewMovePosition = "before" | "after" | "on"
 
type TreeViewMoveEvent = {
  /** Keys being moved. */
  keys: Key[]
  /** The row the drop landed on. */
  targetKey: Key
  /** `before` / `after` reorder next to the target, `on` drops into it. */
  position: TreeViewMovePosition
}

Key is React Aria's Key (string | number), imported from react-aria-components.