Filesystem

Render a collapsible file and folder tree with optional custom row rendering and selection state.

Filesystem is a client component that renders a nested list of folders and files. Use it for file browsers, project trees, and other hierarchical lists. Folders with children expand and collapse with a motion/react height animation, and rows can be customised through render callbacks.

  • src
    • theme.config.ts
    • components
  • docs
  • package.json
Selected: src/theme.config.ts

Import#

typescript
import { Filesystem } from "@reactberry/system/blocks";

Usage#

typescript
import { useState } from "react";
import { Filesystem } from "@reactberry/system/blocks";
import { Box } from "@reactberry/system/elements";

const tree = [
  {
    id: "src",
    name: "src",
    nodes: [
      { id: "src/index.ts", name: "index.ts" },
      { id: "src/components", name: "components", nodes: [{ id: "src/components/Button.tsx", name: "Button.tsx" }] },
    ],
  },
  { id: "package.json", name: "package.json" },
];

export default function ProjectTree() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <Box width="16rem">
      <Filesystem
        nodes={tree}
        initiallyOpenNames={["src"]}
        selectedIds={selected}
        onSelectNode={(node) => node.id && setSelected([node.id])}
      />
    </Box>
  );
}

Examples#

Checkboxes#

Use renderLeading to put a checkbox before the default toggle. Passing the checked ids as selectedIds highlights those rows and provides isSelected.

  • Photos
    • beach.jpg
    • city.jpg
  • invoice.pdf
0 selected

Row metadata#

Use renderNode to add content after the default icon and name, such as the number of items in a folder.

  • app3
    • layout.tsx
    • page.tsx
    • settings3
  • public1

API#

Filesystem#

PropTypeDefaultDescription
nodes*
FilesystemNode[]
—

Top-level nodes. Nothing is rendered when empty.

initiallyOpenNames
string[]
—

Names (not ids) of folders that start expanded, at any depth. Read on first render only.

onSelectNode
((node: FilesystemNode) => void)
—

Called when a row or a folder toggle is clicked.

renderNode
((node: FilesystemNode, defaultContent: ReactNode, ctx: FilesystemNodeContext) => ReactNode)
—

Replaces the icon and name. defaultContent is the default icon and name; ctx is { isSelected }.

renderLeading
((node: FilesystemNode, ctx: FilesystemLeadingContext) => ReactNode)
—

Override the leading slot (toggle for folders, spacer for leaves) so callers can place a fixed-width control such as a checkbox in the same column as the expand toggle.

selectedIds
string[]
—

IDs of nodes considered selected. Selected rows get skin="brand.subtle", and the matching isSelected flag is forwarded to renderNode and renderLeading.

No other props are accepted.

FilesystemNode#

PropTypeDefaultDescription
name*
string
—

Row label. Also matched by initiallyOpenNames.

id
string
—

Used by selectedIds and as part of the React key.

nodes
FilesystemNode[]
—

Child nodes. Any array, even empty, makes the node a folder.

renderLeading context#

PropTypeDefaultDescription
hasChildren
boolean
—

true for folders with at least one child.

isOpen
boolean
—

Whether the folder is expanded.

toggle
() => void
—

Expands or collapses the folder.

defaultLeading
ReactNode
—

The default toggle button, or a spacer for rows without children.

isSelected
boolean
—

Whether the node's id is in selectedIds.

Accessibility#

  • The tree renders as nested ul and li elements. There is no tree role, aria-expanded, or aria-selected.
  • Rows are clickable div elements without keyboard handling. The folder toggle is a native button without an accessible label.

Notes#

  • A node with a nodes array is shown with a folder icon, even when empty; only folders with at least one child get a toggle and can expand.
  • Clicking a row toggles folders with children and calls onSelectNode. The toggle button stops propagation, toggles, and also calls onSelectNode.
  • Open state is local to each row; initiallyOpenNames is read only on first render.
  • Nested levels are not indented by default; the internal depth value is tracked but not used for styling.
  • The root list does not reset listStyleType, while nested lists set it to none.
  • Row names are clamped to one line, with the full name in the row's title attribute.
  • The FilesystemNode and related types are not re-exported from the blocks entry point.