File Tree

A hierarchical file explorer with expand/collapse, drag-and-drop, icons, and multi-selection support.

Usage

src
components
button.tsx
card.tsx
button.css
index.ts
package.json
README.md

Anatomy

Import the FileTree component and access all parts using dot notation.

import { FileTree } from "@blakeui/pro-react";

<FileTree>
  <FileTree.Section>
    <FileTree.Header />
    <FileTree.Item>
      <FileTree.Indicator />
      <FileTree.Item />
    </FileTree.Item>
  </FileTree.Section>
</FileTree>;

Reduced Motion

reduceMotion disables the expand/collapse animation; the user's own reduced-motion preference is always honoured regardless of this prop.

Animated (default)

src
index.ts
app.tsx
styles.css

reduceMotion

src
index.ts
app.tsx
styles.css

With Icons

app
page.tsx
layout.tsx
globals.css
assets
logo.svg
hero.png
inter.woff2
package.json
README.md
.env.local

Custom Indicator

src
index.ts
README.md

Drag and Drop

src
button.tsx
card.tsx
button.css
docs
guide.md
api.md
package.json

Dynamic Collection

packages
core
index.ts
utils.ts
ui
button.tsx
theme.css
turbo.json
README.md

Sizes

sm

src
index.ts
app.tsx

md

src
index.ts
app.tsx

lg

src
index.ts
app.tsx

Guide Lines

showGuideLines=true (default)

src
components
button.tsx
card.tsx
index.ts

showGuideLines="hover"

src
components
button.tsx
card.tsx
index.ts

showGuideLines=false

src
components
button.tsx
card.tsx
index.ts

Multiple Selection

2 selected

src
components
button.tsx
card.tsx
input.tsx
index.ts
README.md

PR File Review

6 changed files
src
components
button.tsx+42-8
card.tsx+17-3
index.ts+4-1
tests
button.test.tsx+96-12
CHANGELOG.md+11-0
package.json+2-2

CSS Classes

Base & Size Classes

  • .file-tree — Root tree container. Flex column, padding: --spacing * 1, scrolls on overflow.
  • .file-tree--sm — Small: --file-tree-indent: --spacing * 2.5, --file-tree-item-px: 10px.
  • .file-tree--md — Medium (default): --file-tree-indent: --spacing * 3, --file-tree-item-px: 12px.
  • .file-tree--lg — Large: --file-tree-indent: --spacing * 6, --file-tree-item-px: 8px.
  • .file-tree__item--sm — Small row: 0.75rem/1rem text, 3.5 * --spacing icons.
  • .file-tree__item--md — Medium row: 0.875rem/1.25rem text, 4 * --spacing icons.
  • .file-tree__item--lg — Large row: 1rem/1.5rem text, 5 * --spacing icons.

Element Classes

  • .file-tree__item — A row. Relative, flex, border-radius: --radius * 1, color: --foreground.
  • .file-tree__item-content — Row layout wrapper. Indents by (--tree-item-level - 1) * --file-tree-indent.
  • .file-tree__guide-line — One 1px absolute vertical rule per ancestor level, painted in --file-tree-guide-color.
  • .file-tree__guide-line--hover — Guide lines start at opacity: 0 and fade in over 0.15s while the tree is hovered.
  • .file-tree__guide-line--none — Guide lines removed (display: none).
  • .file-tree__checkbox — Selection checkbox wrapper, rendered only when selectionMode is not "none" and the selection behaviour is "toggle".
  • .file-tree__chevron — The expand/collapse button, 5 * --spacing square. visibility: hidden until the row has children.
  • .file-tree__indicator — The glyph inside the chevron, 3.5 * --spacing square, color: --muted, transition: rotate 0.15s.
  • .file-tree__icon — Leading icon slot, color: --muted. Sizes its svg per row size.
  • .file-tree__label — The row label. flex: 1, truncates with an ellipsis, user-select: none.
  • .file-tree__section — A group of rows. Adds --spacing * 3 top margin when it isn't the first child.
  • .file-tree__section-header — Section heading. 0.75rem/1rem, font-weight: 500, color: --muted.
  • .file-tree__drag-handle — Grip button, 5 * --spacing square, opacity: 0 at rest, cursor: grab.

Interactive States

  • Hover.file-tree__item:hover / [data-hovered="true"] applies --default. Suppressed while the chevron itself is hovered, via :not(:has(.file-tree__chevron:hover)). Under @media (hover: hover) only.
  • Pressed.file-tree__item:active / [data-pressed="true"] applies --default-hover.
  • Focus visible.file-tree__item:focus-visible / [data-focus-visible="true"] draws a --focus ring offset by --ring-offset-width. The tree root carries the same ring under .file-tree[data-focus-visible].
  • Disabled[aria-disabled="true"] applies --disabled-opacity, --cursor-disabled, and pointer-events: none.
  • Selected[data-selected="true"] applies --default. Adjacent selected rows drop their shared corners so a run reads as one capsule.
  • Expanded[data-expanded] rotates .file-tree__indicator to 90deg.
  • Has children[data-has-child-items] reveals .file-tree__chevron; hovering it applies --default-hover.
  • Dragging[data-dragging] drops the row to opacity: 0.4. The handle fades to 0.4 on row hover and 0.7 on its own hover.
  • Drop target[data-drop-target] applies --accent-soft with a 2px inset --accent outline. The between-rows .react-aria-DropIndicator[data-drop-target] takes a 1px --accent outline.
  • Empty.file-tree[data-empty] centres an italic --muted placeholder at min-height: 100px.
  • Reduced motion@media (prefers-reduced-motion: reduce) removes the indicator's rotate transition.

CSS Variables

  • --file-tree-indent — Indent added per nesting level. Set by the size classes.
  • --file-tree-item-px — Horizontal offset the guide lines are measured from. Set by the size classes.
  • --file-tree-guide-color — Guide-line colour. Defaults to color-mix(in srgb, currentColor 10%, transparent).
  • --tree-item-level — Consumed, not defined: React Aria sets it per row, and .file-tree__item-content multiplies it by --file-tree-indent for the indent.

API Reference

FileTree

The root. Renders React Aria's Tree, and supplies the size and guide-line context every part reads.

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"Row size. Drives row padding, font size, icon size, and indent width together.
showGuideLinesboolean | "hover"trueVertical indentation guides. true always, false never, "hover" fades them in while the tree is hovered.
reduceMotionbooleanfalseDisables the expand/collapse height animation. The user's prefers-reduced-motion setting disables it regardless, so this is an additional opt-out, not an override.
itemsIterable<T>Data for a dynamic collection. Pass a render function as children, and nest with Collection from react-aria-components.
dragAndDropHooksDragAndDropHooksEnables row reordering. Supply the value returned by useFileTreeDrag.
selectionMode"none" | "single" | "multiple""none"Selection behaviour. A checkbox is rendered per row when rows are selectable.
…TreePropsTreeProps<T>Every remaining React Aria Tree prop — defaultExpandedKeys, expandedKeys, onExpandedChange, selectedKeys, onSelectionChange, disabledKeys, onAction.

FileTree.Item

A row. Nest items inside one another to build the hierarchy; a row with child items gets a chevron.

PropTypeDefaultDescription
titleReactNodeThe row label. Accepts arbitrary content — pair it with textValue whenever it isn't a plain string.
iconReactNode | ((state) => ReactNode)A leading icon, or a function of the row's state — {isExpanded, hasChildItems, allowsDragging} — which is how folders show open and closed glyphs.
textValuestringtitleThe row's plain-text value, used for typeahead and assistive tech. Falls back to title when that is a string, so it is required for aReactNode title — otherwise the row has no accessible name.
dragIconReactNode | falseDotted gripThe drag handle, shown only when dragging is enabled. false hides it while leaving the row draggable.
idKeyThe row's key — what expandedKeys, selectedKeys, and disabledKeys refer to.
…TreeItemPropsTreeItemPropsEvery remaining React Aria TreeItem prop, including href and onAction.

FileTree.Indicator

Replaces the expand/collapse chevron. Place it as a direct child of the FileTree.Item it belongs to — it is detected by identity, so wrapping it in another component hides it from the row.

PropTypeDefaultDescription
childrenReactElementChevronRightThe glyph to use. It is cloned with the .file-tree__indicator class, so it inherits the muted colour, the size, and the 90° rotation on expand.
classNamestringAppended to the indicator's own class.

FileTree.Section

Groups rows under an optional header.

PropTypeDefaultDescription
childrenReactNodeAn optional FileTree.Header followed by the section's rows.
classNamestringAppended to the section's own class.
…TreeSectionPropsTreeSectionProps<T>Every remaining React Aria TreeSection prop.

FileTree.Header

The label above a section's rows.

PropTypeDefaultDescription
childrenReactNodeThe heading content.
classNamestringAppended to the header's own class.
…TreeHeaderPropsTreeHeaderPropsEvery remaining React Aria TreeHeader prop.

useFileTree

Derives the values a tree UI usually needs from a nested data array — without walking it by hand.

const { expandableKeys, filterTree, leaves } = useFileTree({ items });
OptionTypeDefaultDescription
itemsT[]The tree data. Each node needs an id and may carry children. Required.
isLeaf(node: T) => booleanno childrenIdentifies non-expandable nodes — override it when an empty folder should still count as a branch.
PropertyTypeDefaultDescription
expandableKeysKey[]Every branch key, in depth-first order — pass it to defaultExpandedKeys for an expand-all.
leavesT[]Every leaf node, flattened — the file count, a search index, a select-all set.
filterTree(predicate: (node: T) => boolean) => T[]Filters by a leaf predicate and prunes branches left empty, so ancestors of a match survive and everything else drops.

useFileTreeDrag

Wires React Aria drag-and-drop to a useTreeData instance, so rows can be reordered and re-parented.

const { dragAndDropHooks } = useFileTreeDrag({ tree });
OptionTypeDefaultDescription
treeFileTreeDataLikeThe useTreeData instance backing the tree. Only getItem, move, moveBefore, and moveAfter are used. Required.
getItems(keys: Set<Key>) => Array<Record<string, string>>text/plainThe drag payload per item. Defaults to each key as plain text.
onMove(keys, target) => voidRuns after a successful move, with the moved keys and the resolved drop target.
PropertyTypeDefaultDescription
dragAndDropHooksDragAndDropHooksPass straight to the tree's dragAndDropHooks prop. Drops resolve to moveBefore, moveAfter, or an append via move, depending on where the row lands.

On this page