# Build a File Tree / Folder Navigation Component

Source: https://www.techinterview.org/post/3233475210/build-file-tree-folder-navigation/
Updated: 2026-07-26 · techinterview.org

File-tree navigation appears in IDEs ([VS Code](/companies/cursor/)), file managers, document apps (Google Drive), and CMSes. The interview tests whether you understand recursive rendering, lazy loading, virtualization, and keyboard navigation patterns.

## Functional requirements

- Render tree of folders and files

- Click folder → expand/collapse

- Click file → select / open

- Lazy-load children when expanding (for large trees)

- Keyboard navigation

- Drag-drop to reorganize

- Context menu (rename, delete)

## Data structure

Tree nodes:


```
{
  id: string,
  name: string,
  type: 'folder' | 'file',
  children?: Node[],
  isLoaded?: boolean // for lazy loading
}
```


## Recursive rendering

Each node renders itself and (if folder + expanded) its children:


```
function TreeNode({ node, depth }) {
  const [expanded, setExpanded] = useState(false);
  return (
    <>
      <div onClick={() => setExpanded(!expanded)} style={{ paddingLeft: depth * 16 }}>
        {node.name}
      </div>
      {expanded && node.children?.map(c => (
        <TreeNode key={c.id} node={c} depth={depth + 1} />
      ))}
    </>
  );
}
```


## Lazy loading

For trees with thousands of nodes, don't load everything:

- Folder shows expand chevron

- Click expand → fetch children if not yet loaded

- Show loading indicator

- Cache loaded children so collapsing and re-expanding a folder reuses the data instead of refetching. Track this with the `isLoaded` flag and skip the network call when it's already true.

## Virtualization

For long trees (10K+ nodes visible), virtualize:

- react-arborist: built specifically for trees. It handles flattening, virtualization, drag-drop, and keyboard nav out of the box, so you reach a working tree with far less code than rolling your own.

- react-window with manual flattening. You convert the nested tree into a flat array of only the visible rows yourself, then let react-window render the window. More work, but full control over how each row renders.

Render only visible rows. Calculate item heights from depth + collapsed/expanded state.

## Keyboard navigation

WAI-ARIA tree pattern:

- **Down:** next visible item

- **Up:** previous visible item

- **Right:** expand if collapsed; first child if expanded

- **Left:** collapse if expanded; parent if collapsed

- **Home:** first item

- **End:** last visible item

- **Enter:** select / open

## Selection

- Single selection: click highlights. Keep one selected id in state; interviewers check that clicking a new row clears the old selection cleanly.

- Multi-selection: Cmd+click toggles individual; Shift+click selects range. Range selection needs an anchor node — Shift+click selects every row between that anchor and the clicked row in visible (flattened) order, so you need the flat list to compute it.

- Indicate selected with background color, and mirror it with `aria-selected` so the state is announced to screen readers, not only shown visually.

## Drag and drop

Drag a file/folder onto another folder to move:

- Use HTML5 DnD or a library (dnd-kit)

- Drop indicator shows where the item will land

- Validate: do not drop folder into its own descendant

- Auto-expand folders on hover during drag

## Context menu

Right-click on item → menu with actions (rename, delete, copy, paste).

- Position menu near cursor; flip if near viewport edge

- Keyboard accessible (context-menu key on Windows/Linux)

- Close on click outside or Escape

## Performance

- Memoize node components. Wrap `TreeNode` in `React.memo` so a node only re-renders when its own props change, not every time a distant sibling expands.

- Use stable IDs for keys. Key rows by the node id, never the array index, so React reuses DOM on reorder or insert instead of remounting rows and losing focus.

- Avoid re-rendering entire tree on small changes. Keep expanded and selected state in a `Set` or map keyed by id rather than mutating the tree objects, so toggling one folder updates one node's props.

## Common mistakes

- Re-rendering all nodes on every state change. Storing expansion state on the tree object forces a full re-render on each toggle; a tree that stutters at 1K nodes is a common red flag.

- Loading entire tree upfront. Fetching every node blocks first paint and wastes bandwidth on folders the user never opens.

- No keyboard support. Interviewers almost always probe arrow-key navigation and Enter-to-open; skipping it signals you overlooked accessibility.

- Drag-drop allows invalid moves (folder into descendant). Without the descendant check you create a cycle and orphan the moved subtree.

- Selection state not visually clear. Users lose track of what's targeted right before a rename or delete, which turns into destructive mistakes.

## ARIA

- Wrapper: `role="tree"` — the single container that owns arrow-key focus for the whole widget.

- Each node: `role="treeitem"` — one per visible row, folder or file.

- `aria-expanded` for folders — set true or false so a screen reader announces open or closed; leave it off files, since a leaf has nothing to expand.

- `aria-selected` for selected items — mirror your visual highlight so selection is spoken, not only colored.

- `aria-level` for depth — the 1-based nesting depth, which lets assistive tech convey how deep a node sits.

- `aria-setsize` and `aria-posinset` for siblings count — together they let a screen reader announce "item 3 of 7" within a folder.

## Frequently Asked Questions

### Should I use react-arborist or react-window?

react-arborist is built for trees specifically. react-window with manual flattening is more flexible but more code.

### How do I handle a folder being renamed?

Inline edit: double-click name → input field. Save on blur or Enter. Update tree state and persist.

### What about file icons?

Map file extension to icon. Library: vscode-icons, file-icons-js. Or build a small icon set.
