Skip to content

shadcn.io is not affiliated with official shadcn/ui

Components

Free

File Tree for Solid

The files an agent changed, as folders to open and close. For Solid with Ark UI, a Collapsible per folder, a Button per file, no component of its own.

UI library: Ark UI

The files the agent changed show as a tree of folders, each open to start with, and the file the reader picked stays marked.

Use it at the end of a turn that touched several files, so the reader sees where the changes are and opens the one they want. A folder is a Collapsible and a file is a Button, so there is no File Tree component to install in Solid. For a single file the agent read or edited, use a Tool line instead. For a large tree the reader browses with the arrow keys, with selection and lazy loading, use a Tree View.

Preview width

Installation

File Tree is a pattern. The command adds the parts it's built from, Collapsible and Button, and the code to copy is the demo's.

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Anatomy

File Tree anatomy: trigger, content, file, collapsible, selected
Collapsible (one per folder)
├── CollapsibleTrigger (a ghost Button)
│   ├── ChevronRightIcon
│   └── FolderIcon
└── CollapsibleContent
    └── div (ms-4)
        ├── Collapsible (a folder inside)
        └── Button (one per file)

Usage

Render the nodes recursively, and nest each folder's children in its CollapsibleContent.

import { ChevronRightIcon, FileIcon, FolderIcon } from "lucide-solid";
import { Button } from "@/components/ui/button";
import {
  Collapsible, CollapsibleContent, CollapsibleTrigger,
} from "@/components/ui/collapsible";
interface FileNode {
  children?: FileNode[];
  name: string;
  path: string;
  type: "file" | "folder";
}

export function FileTreeNode(props: {
  node: FileNode;
  onSelect: (path: string) => void;
}) {
  const { node, onSelect } = props;

  if (node.type === "folder") {
    return (
      <Collapsible defaultOpen>
        <CollapsibleTrigger
          asChild={(props) => (
            <Button {...props()} class="w-full justify-start" size="sm" variant="ghost">
              <ChevronRightIcon
                aria-hidden="true"
                class="transition-transform group-aria-expanded/button:rotate-90 rtl:-scale-x-100 rtl:group-aria-expanded/button:-rotate-90"
              />
              <FolderIcon aria-hidden="true" />
              {node.name}
            </Button>
          )}
        />
        <CollapsibleContent>
          <div class="ms-4 flex flex-col">
            {node.children?.map((child) => (
              <FileTreeNode key={child.path} node={child} onSelect={onSelect} />
            ))}
          </div>
        </CollapsibleContent>
      </Collapsible>
    );
  }

  return (
    <Button
      class="w-full justify-start"
      onClick={() => onSelect(node.path)}
      size="sm"
      variant="ghost"
    >
      <FileIcon aria-hidden="true" />
      {node.name}
    </Button>
  );
}

The chevron turns on the trigger's own aria-expanded, through the Button's group/button, so a closed folder inside an open one keeps its chevron pointing at the name. It points to the reading end, and down once open, in both directions. The demo marks the picked file with aria-current and the accent background, and its folder buttons keep the ghost hover instead of Button's open fill. See Collapsible.

AI SDK

The tree comes from the paths the agent wrote. Collect them from the turn's tool parts (here a writeFile tool with a path input), then fold the flat paths into folders.

const paths = message.parts.flatMap((part) =>
  part.type === "tool-writeFile" && part.state === "output-available"
    ? [part.input.path]
    : []
);
/** "app/page.tsx" and "app/layout.tsx" become the app folder with two files. */
function toTree(paths: string[]): FileNode[] {
  const root: FileNode[] = [];
  for (const path of paths) {
    const names = path.split("/");
    let level = root;
    names.forEach((name, index) => {
      const at = names.slice(0, index + 1).join("/");
      if (index === names.length - 1) {
        level.push({ name, path: at, type: "file" });
        return;
      }
      let folder = level.find(
        (node) => node.type === "folder" && node.path === at
      );
      if (!folder) {
        folder = { children: [], name, path: at, type: "folder" };
        level.push(folder);
      }
      folder.children ??= [];
      level = folder.children;
    });
  }
  return root;
}

The parts' shape is the AI SDK's own on every target, so the same code reads useChat's messages in React and Chat's in Vue and Svelte.

Migrating from AI SDK Elements

AI SDK Elements is for React. In Solid, start from the demo above: its folders and files are all there is to File Tree.

FAQ

What does installing File Tree add in Solid?

Collapsible and Button, the two parts the tree is built from. File Tree is a pattern, so no file-tree file lands in your ui folder: copy the demo's code into your own component and give it the agent's paths. The command uses the shadcn CLI.

When should I use Tree View instead?

When the tree is something to browse rather than a short list of changes: hundreds of nodes, arrow-key navigation, multiple selection, or children loaded on demand. Tree View has a tree's roles and keyboard. File Tree is a handful of folders the reader opens with a click.

How does the keyboard work?

Every folder and file is a button, so Tab moves through them in order, and Enter or Space opens or closes a folder or picks a file. A closed folder's files are out of the tab order. There are no arrow keys, as in any list of buttons.

How is the picked file shown?

The demo keeps the picked path in state, gives its Button the accent background, and sets aria-current on it so assistive tech hears which one is current. Opening that file in a panel or a Code Block is up to you.

Can folders start closed?

Yes. Drop defaultOpen from the folder's Collapsible, or set it only for the folders on the way to the changed files. Open is the right default for an agent's changes: the reader came to see the files, not the folders.

Does the tree work right to left?

Yes. The children are indented with ms-4, which follows the reading direction, and the chevron is flipped so a closed folder points at its name. An open folder's chevron points down in both directions.

Is this the same as AI SDK Elements' File Tree?

It does the same job without a wrapper: a folder was a FileTreeFolder and is now a Collapsible, and a file was a FileTreeFile and is now a Button. The Migrating from AI SDK Elements section has a prompt that rebuilds the old file on these parts and keeps its exports.

API Reference

File Tree has no props of its own. Each folder is a Collapsible: defaultOpen and the trigger and content are Collapsible's. Each folder's trigger and each file is a Button, with variant and size from Button.