Skip to content
Entangle UI v0.13.0

FileTree

A file-system-flavored specialization of TreeView. It maps a file/folder model onto the tree, auto-assigns file-type icons by extension (plus open/closed folder glyphs), and adds drag-and-drop import of OS files onto a folder or the root. Expansion, selection, keyboard navigation, and sizing are inherited from TreeView unchanged.

Live Preview

import { FileTree } from 'entangle-ui';
import type { FileTreeNode } from 'entangle-ui';

FileTree takes a nested array of FileTreeNodes. Each node has a kind of 'file' or 'folder'; folders carry children. File-type icons are derived from the file name (or an explicit ext), so you don’t assign them yourself.

const nodes: FileTreeNode[] = [
{
id: 'src',
name: 'src',
kind: 'folder',
children: [
{ id: 'btn', name: 'Button.tsx', kind: 'file' },
{ id: 'logo', name: 'logo.svg', kind: 'file' },
],
},
{ id: 'readme', name: 'README.md', kind: 'file' },
];
<FileTree nodes={nodes} defaultExpandedIds={['src']} />;

Clicking anywhere on a folder row toggles it open/closed — not just the chevron (expandOnClick, on by default). Files are unaffected. Set expandOnClick={false} to require a chevron click instead.

// Whole-row toggle (default)
<FileTree nodes={nodes} />
// Require the chevron
<FileTree nodes={nodes} expandOnClick={false} />

Provide onImport to enable the import drop zone. Dropping OS files onto a folder targets that folder; dropping a file row targets its parent folder; dropping on empty space targets the root (targetFolder: null). The active target folder is highlighted while dragging. FileTree only reports intent — apply the change to your data and pass back fresh nodes.

Drag-and-drop import

<FileTree
nodes={nodes}
onImport={({ files, targetFolder }) => {
uploadInto(targetFolder?.id ?? 'root', files);
}}
/>

Icons are resolved from the file extension into a small set of buckets (image, media, code, archive, text) backed by the library icon set, with open/closed folder glyphs. Pass resolveIcon to override per node — return undefined to fall back to the built-in icon.

File-type icons

<FileTree
nodes={nodes}
resolveIcon={(node, { expanded }) =>
node.name.endsWith('.json') ? <StarIcon color="warning" /> : undefined
}
/>

The classifyExtension / getFileIconKind helpers used internally are also exported, in case you want to drive your own UI from the same mapping.

Built-in icons follow the theme (folders use text.secondary, files text.muted), so a theme change re-colors them. For per-type or per-node colors, return a colored icon from resolveIcon — every library icon takes a color prop (a theme key like accent / success / warning, or any CSS color).

Colored icons by type

<FileTree
nodes={nodes}
resolveIcon={(node, { expanded }) => {
if (node.kind === 'folder') {
return expanded ? (
<FolderOpenIcon color="accent" />
) : (
<FolderIcon color="accent" />
);
}
if (node.name.endsWith('.png')) return <ImageIcon color="success" />;
return undefined; // fall back to the built-in icon
}}
/>

Selection reuses TreeView’s controlled/uncontrolled model verbatim — single (default), multiple (Ctrl / Shift click, Ctrl+A), or none.

Selection

<FileTree
nodes={nodes}
selectionMode="multiple"
selectedIds={selected}
onSelectionChange={setSelected}
/>

Sizes

<FileTree nodes={nodes} size="sm" />
<FileTree nodes={nodes} size="md" />
<FileTree nodes={nodes} size="lg" />

FileTree renders a TreeView internally and feeds it derived nodes — it does not reimplement expansion, selection, or keyboard navigation. The same controlled/uncontrolled props (expandedIds / defaultExpandedIds, selectedIds / defaultSelectedIds) and the same keyboard behaviour apply. Reach for TreeView directly when you need a generic hierarchy; reach for FileTree when the data is files and folders.

The drag-and-drop import is built on top of TreeView’s generic drop-target props (dropTargetId, onNodeDragOver / onNodeDragLeave / onNodeDrop), which are available for building your own drop interactions on a plain TreeView as well.

FileTree is themed entirely through the --etui-* theme contract — no colors, spacings, or fonts are hard-coded. There are three ways to restyle it.

  • className / style are applied to the root container (style is also the place to set --etui-* overrides — they cascade to every row).
  • resolveIcon swaps a node’s icon; renderNode replaces a row’s whole inner content (icon + label); renderActions adds trailing controls.

The internal class names are compiled (hashed) by Vanilla Extract, so target the stable structural hooks instead:

| Selector | Matches | | -------------------------------------------- | ---------------------------------------------------- | | [role="tree"] | The tree region. | | [role="treeitem"] | Every row. | | [role="treeitem"][aria-selected="true"] | Selected rows. | | [role="treeitem"][data-drop-target="true"] | The folder currently highlighted as a drop target. | | [data-root-active="true"] | The container while files are dragged over the root. | | #treenode-<id> | A specific row by node id. |

Override these on any ancestor to re-skin FileTree (or use createCustomTheme(...) for a whole-app palette):

| Token | Used for | | -------------------------------------------- | -------------------------------------------------------------------- | | --etui-color-accent-primary | Selected-row tint and the active drop-target highlight (row + root). | | --etui-color-surface-hover | Row hover background. | | --etui-color-border-focus | Focused-row ring. | | --etui-color-border-default | Guide lines. | | --etui-color-text-primary | File / folder names; chevron hover. | | --etui-color-text-secondary | Folder icons. | | --etui-color-text-muted | File icons, chevrons, and empty-state text. | | --etui-radius-sm | Container corner rounding. | | --etui-spacing-xs, --etui-spacing-md | Icon gap / row padding; empty-state padding. | | --etui-font-size-md, --etui-font-size-lg | Size-scaled label text. | | --etui-line-height-normal | Label line height. | | --etui-transition-fast | Chevron rotation and hover transitions. |

Re-skinned via token overrides

<FileTree
nodes={nodes}
style={{
['--etui-color-accent-primary' as string]: '#d946ef',
['--etui-color-text-secondary' as string]: '#d946ef',
}}
/>

FileTree renders no copy of its own — file and folder names come from your data, and the file-type icons are decorative. The only built-in strings are the tree’s accessible name and the empty-state text, both overridable through the labels prop (a Partial<FileTreeLabels>, so anything you omit keeps its English default). An explicit aria-label / emptyContent still wins over the matching label.

Localized labels (Polish)

<FileTree
nodes={nodes}
labels={{ treeLabel: 'Drzewo plików', emptyLabel: 'Brak plików' }}
/>

The English defaults are exported as DEFAULT_FILE_TREE_LABELS (spread and tweak it when you only need to change one key).

| Key | Type | Default | | ------------ | -------- | ------------- | | treeLabel | string | "File tree" | | emptyLabel | string | "No files" |

Prop Type Default Description
nodes FileTreeNode[] Tree data — array of root-level files/folders.
expandedIds string[] Expanded node IDs (controlled).
defaultExpandedIds string[] Default expanded node IDs (uncontrolled).
onExpandedChange (expandedIds: string[]) => void Fired when expanded nodes change.
selectedIds string[] Selected node IDs (controlled).
defaultSelectedIds string[] Default selected node IDs (uncontrolled).
selectionMode 'single' | 'multiple' | 'none' 'single' Selection mode (reused from TreeView).
onSelectionChange (selectedIds: string[]) => void Fired when selected nodes change.
size 'sm' | 'md' | 'lg' 'md' Row size.
indent number 16 Indentation per depth level in pixels.
showChevrons boolean true Whether to show expand/collapse chevrons for folders.
showGuideLines boolean false Whether to show connecting guide lines.
expandOnClick boolean true Toggle a folder open/closed when its whole row is clicked (not just the chevron).
maxHeight number | string Maximum height before the tree scrolls.
resolveIcon (node: FileTreeNode, state: { expanded: boolean }) => ReactNode Override a node icon. Return undefined to fall back to the built-in extension map.
renderNode (node: FileTreeNode, state: FileTreeNodeState) => ReactNode Fully replace a node's inner content (icon + label).
renderActions (node: FileTreeNode, state: FileTreeNodeState) => ReactNode Render trailing actions on the right of a row.
emptyContent ReactNode Content shown when nodes is empty.
onImport (payload: { files: File[]; targetFolder: FileTreeNode | null }) => void Fired when OS files are dropped onto a folder or the root. Presence enables the import drop zone.
onNodeClick (node: FileTreeNode, event: MouseEvent) => void Fired when a node is clicked.
onNodeDoubleClick (node: FileTreeNode, event: MouseEvent) => void Fired when a node is double-clicked.
onNodeContextMenu (node: FileTreeNode, event: MouseEvent) => void Fired when a node is right-clicked.
labels Partial<FileTreeLabels> Override built-in strings (tree accessible name, empty-state text). Merged onto the defaults.
aria-label string Accessible name for the tree. Wins over labels.treeLabel; lands on the role="tree" element.
className string Additional CSS class names (on the root container).
testId string Test identifier for automated testing.

| Property | Type | Description | | ---------- | ------------------------- | -------------------------------------------------- | | id | string | Stable unique id within the tree. | | name | string | Display name, e.g. "Button.tsx". | | kind | 'file' \| 'folder' | File vs. folder. Folders accept dropped files. | | ext | string | Extension override (no dot). Inferred from name. | | path | string | Optional path (informational; surfaced unchanged). | | children | FileTreeNode[] | Child entries (folders). | | disabled | boolean | Dim + non-interactive. | | data | Record<string, unknown> | Arbitrary consumer payload. |

Accessibility is inherited from TreeView:

  • The container is role="tree"; each row is role="treeitem" with aria-expanded / aria-selected and aria-level.
  • aria-activedescendant tracks the focused node; focus is scrolled into view.
  • Full keyboard navigation (Arrow keys, Home/End, Enter, Space, Ctrl+A, Shift+Arrow range selection). File-type icons are decorative (aria-hidden), so the file name is the accessible label.