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
import { FileTree } from 'entangle-ui';import type { FileTreeNode } from 'entangle-ui';Usage
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']} />;Expanding folders
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} />Drag-and-drop import
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); }}/>File-type icons
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.
Coloring icons
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
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
Sizes
<FileTree nodes={nodes} size="sm" /><FileTree nodes={nodes} size="md" /><FileTree nodes={nodes} size="lg" />Relationship to TreeView
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.
Styling
FileTree is themed entirely through the --etui-* theme contract — no colors,
spacings, or fonts are hard-coded. There are three ways to restyle it.
Render & content overrides
className/styleare applied to the root container (styleis also the place to set--etui-*overrides — they cascade to every row).resolveIconswaps a node’s icon;renderNodereplaces a row’s whole inner content (icon + label);renderActionsadds trailing controls.
Targeting hooks
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. |
Theme tokens consumed
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', }}/>Internationalization
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" |
Props
| 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. |
FileTreeNode
| 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
Accessibility is inherited from TreeView:
- The container is
role="tree"; each row isrole="treeitem"witharia-expanded/aria-selectedandaria-level. aria-activedescendanttracks 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.