Tree view
Also called file tree, folder tree, tree navigation, or nested list
A nested list of folders and files that open and close in place, with indent guides and arrow-key navigation, like a code editor’s sidebar.
import { useState, type KeyboardEvent } from 'react';
export type TreeNode = { id: string; name: string; children?: TreeNode[] };
export function FileTree({ nodes }: { nodes: TreeNode[] }) {
const [open, setOpen] = useState<Record<string, boolean>>({});
// Only the active row is in the tab order, so the tree is one tab stop.
const [active, setActive] = useState(nodes[0]?.id);
const toggle = (id: string, next = !open[id]) =>
setOpen({ ...open, [id]: next });
function onKeyDown(event: KeyboardEvent<HTMLUListElement>) {
// Closed folders render no children, so DOM order is the visible order.
const rows = [
...event.currentTarget.querySelectorAll<HTMLElement>('[role=treeitem]')
];
const row = event.target as HTMLElement;
const [index, id] = [rows.indexOf(row), row.dataset.id ?? ''];
const folder = row.hasAttribute('aria-expanded');
if (event.key === 'ArrowRight' && folder && !open[id]) toggle(id, true);
else if (event.key === 'ArrowLeft' && open[id]) toggle(id, false);
else if (event.key === 'Enter' && folder) toggle(id);
else {
const next = {
ArrowDown: rows[index + 1],
ArrowUp: rows[index - 1],
Home: rows[0],
End: rows.at(-1),
ArrowRight: folder ? rows[index + 1] : undefined,
ArrowLeft: row.parentElement?.closest<HTMLElement>('[role=treeitem]')
}[event.key];
if (!next) return;
next.focus();
}
event.preventDefault();
}
const renderRows = (list: TreeNode[], level: number) =>
list.map((node) => (
<li
key={node.id}
role='treeitem'
data-id={node.id}
aria-level={level}
aria-expanded={node.children ? !!open[node.id] : undefined}
aria-selected={node.id === active}
tabIndex={node.id === active ? 0 : -1}
onFocus={(event) => {
event.stopPropagation();
setActive(node.id);
}}
className='outline-none focus-visible:[&>div]:ring-2 focus-visible:[&>div]:ring-blue-500'
>
<div
onClick={() => node.children && toggle(node.id)}
className={`flex items-center gap-1.5 rounded-md px-2 py-1 ${node.id === active ? 'bg-blue-500/10' : ''}`}
>
<span
aria-hidden='true'
className={`transition-transform ${node.children ? '' : 'invisible'} ${open[node.id] ? 'rotate-90' : ''}`}
>
›
</span>
{node.name}
</div>
{node.children && open[node.id] && (
// The left border doubles as the indent guide.
<ul role='group' className='ml-3.5 border-l pl-2'>
{renderRows(node.children, level + 1)}
</ul>
)}
</li>
));
return (
<ul role='tree' aria-label='Files' onKeyDown={onKeyDown} className='w-64'>
{renderRows(nodes, 1)}
</ul>
);
}Build a file tree for the project sidebar: folders with a chevron that rotates 90° when open, folder and file icons, 16px of indent per level, and a 1px vertical guide line under each open folder. Clicking a folder toggles it with a 180ms height animation, and clicking a file selects it with a light blue row. Follow the ARIA tree pattern (role="tree" and "treeitem", aria-expanded, aria-level, one tab stop) with Up, Down, Left, Right, Home, and End keys, and skip the animation under prefers-reduced-motion.
Paste into Layerwise or any AI coding tool.
What is Tree view?
A tree view shows a hierarchy as rows. Each folder has a chevron that turns when it opens, and its children appear beneath it, indented one step, often with a thin vertical guide line that shows which folder they belong to. Clicking a folder toggles it; clicking a file selects it.
Use it for anything that nests more than two levels deep: files in a project, pages in a docs site, categories in a store admin, layers in a design tool. For a single level of grouping, an accordion or a grouped list is simpler.
Follow the ARIA tree pattern so the keyboard works: the list gets role="tree", rows get role="treeitem" with aria-expanded and aria-level, and the whole tree is one tab stop. Up and Down move between visible rows, Right opens a folder or steps into it, Left closes it or jumps to its parent, and Home and End go to the first and last rows.
Open folders with a quick height animation, around 150 to 200ms, and skip it under reduced motion. For very large trees, such as a repository with thousands of files, render only the rows on screen and load a folder’s children the first time it opens.
Often confused with
- Accordion
- An accordion opens flat sections of content on a page. A tree view nests items many levels deep and lets people move through them with the arrow keys.
Related terms
All termsAccordion
A vertical stack of headings that each expand to reveal their content when clicked, so long pages stay short and easy to scan.
Sidebar
A vertical panel along the side of an app that holds the main navigation, often collapsible to a slim strip of icons.
Height auto animation
An animation that opens or closes a panel to the exact height of its content, even though that height is unknown ahead of time.
Focus ring
An outline that marks which element has keyboard focus, so people moving through a page with the Tab key can see where they are.
Build it with Layerwise
Describe your app, watch it run in a live preview, and publish it with a database, storage, and sign-in built in.