Skip to content

Tree View

Browse and manage nested data with selection, lazy loading and inline renaming.

import { TreeView, TreeViewContextActions } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use a tree view for a hierarchy people browse and act on: files in a project, pages in a site, folders to sync, categories and their items. It's a WAI-ARIA tree with one Tab stop and the full keyboard model. For two levels of links in an app's navigation, use a Sidebar. For sections of content that open and close, use an Accordion.

import { TreeView, type TreeNode } from '@youj/react'

const files: TreeNode[] = [
  {
    id: 'src',
    name: 'src',
    children: [
      { id: 'src/app.tsx', name: 'app.tsx' },
      { id: 'src/main.tsx', name: 'main.tsx' },
    ],
  },
  { id: 'package.json', name: 'package.json' },
]

export function Files() {
  return <TreeView aria-label="Files" data={files} defaultExpanded={['src']} onAction={(node) => openFile(node.id)} />
}

A node with children is a branch, and one without is a leaf. Folders get icons that open with them and files get icons by type; set icons={false} for a plain tree, or give a node its own icon. meta puts a status letter or a count at the end of the row. It's decorative, so say the same thing in description, which is read after the name.

Selection and open branches can each be controlled (selected, expanded) or left to the tree (defaultSelected, defaultExpanded).

Reference

Propsdata, expanded, defaultExpanded… (14)

Props

TreeView props
PropType
dataRequiredTreeNode[]–
expandedstring[]

Open branches.

–
defaultExpandedstring[]

default []

[]
onExpandedChange(ids: string[]) => void–
selectionMode'single' | 'multiple'

single selects one item; multiple is a checkbox tree with tri-state branches.

default 'single'

'single'
selectedstring[]–
defaultSelectedstring[]

default []

[]
onSelectedChange(ids: string[]) => void–
onAction(node: TreeNode) => void

Enter or double-click on a leaf: open the file.

–
loadChildren(node: TreeNode) => Promise<unknown>

Loads a lazy branch's children. Resolve once data holds them; reject to show a retry row.

–
onRename(node: TreeNode, name: string) => void

Enables F2 / inline rename. Called with the trimmed new name when it changed.

–
contextMenu(node: TreeNode, actions: TreeViewContextActions) => ReactNode

Context-menu items for a node (ContextMenuItem and friends).

–
iconsboolean

File-explorer icons: folders that open, files by type.

default true

true
virtualboolean

Window the rows for large trees. Give the tree a height.

default false

false
Other props pass through: extends ComponentProps<'ul'>.

A node (TreeNode) has an id and a name, and optionally children, hasChildren for a lazy branch, an icon, meta, a description and disabled. TreeFileIcon and TreeFolderIcon are the tree's icons, for use beside it, as the editor tabs in a file explorer would.

MotionBranch opens, branch closes, chevron… · reduced motion

Motion

Motion tokens
MomentTokens
Branch opensheight to auto and opacity (Collapse); closing mid-open reverses from where it is
duration.baseease.standard

220ms · cubic-bezier(0.2, 0, 0, 1)

220mscubic-bezier(0.2, 0, 0, 1)
Branch closesheight on base, opacity out on fast
duration.baseease.standard

220ms · cubic-bezier(0.2, 0, 0, 1)

220mscubic-bezier(0.2, 0, 0, 1)
Chevronturns a quarter as the branch opens
no token

Folderits front panel tips open with the branch
no token

Row hover and selectionbackground
duration.instantease.standard

80ms · cubic-bezier(0.2, 0, 0, 1)

80mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

Branches open and close instantly, and the chevron and folder switch state without turning.

Reduced motion

Branches appear and disappear at once, with no height animation, and the chevron and folder show their new state without turning or tipping. Everything else, including the loading row and the announcements, is the same.

AccessibilityKeyboard, ARIA, focus

Accessibility

  • It's a tree of treeitems, named by aria-label, with aria-expanded on branches and aria-level, aria-posinset and aria-setsize on every item.
  • One Tab stop: the focused item carries tabIndex={0} and the rest -1, so Tab moves past the tree in one step.
  • Single selection uses aria-selected. The checkbox tree uses aria-checked, mixed included. Disabled items are aria-disabled.
  • A loading branch is aria-busy, and loads are announced: "Loading node_modules", then "node_modules loaded" or "Couldn't load node_modules". Renames are announced too.
KeyDoes
↑ ↓Move between visible items.
→Opens a closed branch, or moves into an open one. Flipped under right-to-left text.
←Closes an open branch, or moves out to the parent.
Home / EndThe first or last visible item.
*Opens every sibling branch.
TypingJumps to the next item whose name starts with the letters typed.
EnterOpens a file (onAction) or toggles a folder. Retries a failed load.
SpaceSelects the item, or checks it in a checkbox tree.
F2Renames it, with onRename.
Shift+F10Opens its context menu, with contextMenu.