# File Tree

The files an agent changed, shown as a folder tree you can open, with the selected file marked.

Source: https://suiss.app/ui/docs/ai/file-tree

**React**

**Example**

```tsx
"use client"

import { CaretRightIcon, FileIcon, FolderIcon } from "@phosphor-icons/react"
import * as React from "react"
import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { cn } from "@/lib/utils"

type FileNode =
  | { type: "file"; name: string; path: string }
  | { type: "folder"; name: string; path: string; children: FileNode[] }

const files: FileNode[] = [
  {
    type: "folder",
    name: "app",
    path: "app",
    children: [
      { type: "file", name: "layout.tsx", path: "app/layout.tsx" },
      { type: "file", name: "page.tsx", path: "app/page.tsx" },
    ],
  },
  {
    type: "folder",
    name: "components",
    path: "components",
    children: [
      {
        type: "folder",
        name: "ui",
        path: "components/ui",
        children: [
          {
            type: "file",
            name: "button.tsx",
            path: "components/ui/button.tsx",
          },
        ],
      },
      {
        type: "file",
        name: "login-form.tsx",
        path: "components/login-form.tsx",
      },
    ],
  },
]

function FileTreeNode({
  node,
  selected,
  onSelect,
}: {
  node: FileNode
  selected: string
  onSelect: (path: string) => void
}) {
  if (node.type === "folder") {
    return (
      <Collapsible defaultOpen>
        <CollapsibleTrigger
          render={
            <Button
              variant="ghost"
              size="sm"
              className="group/folder w-full justify-start font-normal aria-expanded:bg-transparent aria-expanded:hover:bg-accent"
            />
          }
        >
          <CaretRightIcon
            className="transition-transform group-aria-expanded/folder:rotate-90 rtl:rotate-180 rtl:group-aria-expanded/folder:rotate-90"
          />
          <FolderIcon />
          {node.name}
        </CollapsibleTrigger>
        <CollapsibleContent>
          <div className="ms-4 flex flex-col">
            {node.children.map((child) => (
              <FileTreeNode
                key={child.path}
                node={child}
                selected={selected}
                onSelect={onSelect}
              />
            ))}
          </div>
        </CollapsibleContent>
      </Collapsible>
    )
  }

  return (
    <Button
      variant="ghost"
      size="sm"
      className={cn(
        "w-full justify-start font-normal",
        selected === node.path && "bg-accent text-accent-foreground"
      )}
      aria-current={selected === node.path ? "true" : undefined}
      onClick={() => onSelect(node.path)}
    >
      <FileIcon />
      {node.name}
    </Button>
  )
}

export default function FileTreeDemo() {
  const [selected, setSelected] = React.useState("components/ui/button.tsx")

  return (
    <div className="flex w-full max-w-xs flex-col py-6 text-sm">
      {files.map((node) => (
        <FileTreeNode
          key={node.path}
          node={node}
          selected={selected}
          onSelect={setSelected}
        />
      ))}
    </div>
  )
}
```

File Tree shows the files the agent changed as folders you can open. A folder is a [Collapsible](https://suiss.app/ui/docs/components/collapsible) and a file is a [Button](https://suiss.app/ui/docs/components/button). For a single file the agent read or edited, use a [Tool](https://suiss.app/ui/docs/ai/tool) row instead.

## Installation

File Tree is built with the [Collapsible](https://suiss.app/ui/docs/components/collapsible) and [Button](https://suiss.app/ui/docs/components/button) components.

```bash title="Terminal"
npx shadcn@latest add @esuiss/collapsible @esuiss/button
```

Or use the [suiss/ui CLI](https://suiss.app/ui/docs/cli):

```bash title="Terminal"
npx @esuiss/ui@latest add collapsible button
```

## Usage

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

```tsx
if (node.type === "folder") {
  return (
    <Collapsible defaultOpen>
      <CollapsibleTrigger
        render={
          <Button
            variant="ghost"
            size="sm"
            className="group/folder w-full justify-start"
          />
        }
      >
        <ChevronRightIcon className="transition-transform group-aria-expanded/folder:rotate-90" />
        <FolderIcon />
        {node.name}
      </CollapsibleTrigger>
      <CollapsibleContent>
        <div className="ms-4 flex flex-col">
          {node.children.map((child) => (
            <FileTreeNode key={child.path} node={child} />
          ))}
        </div>
      </CollapsibleContent>
    </Collapsible>
  )
}
```

A file is a ghost button that selects it. Set `aria-current` on the selected file.

```tsx
<Button
  variant="ghost"
  size="sm"
  className="w-full justify-start"
  aria-current={selected === node.path ? "true" : undefined}
>
  <FileIcon />
  {node.name}
</Button>
```

## API Reference

See [Collapsible](https://suiss.app/ui/docs/components/collapsible#api-reference) and [Button](https://suiss.app/ui/docs/components/button#api-reference).

## Accessibility

- Each folder is a button with `aria-expanded`, and opens and closes with Enter or Space.
- Mark the selected file with `aria-current` as well as a background, so the selection is announced.
- The chevron and the folder and file icons are decorative; the name is the accessible label.

**React Native, SwiftUI, Jetpack Compose**

```tsx
import { FileIcon, FolderIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceDescription, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function AiFileTreeExample() {
  return (
    <Trace>
      <TraceCollapsible defaultOpen>
        <TraceCollapsibleTrigger>
          <TraceLine>
            <TraceIcon>
              <Icon as={FolderIcon} />
            </TraceIcon>
            <TraceTitle>app</TraceTitle>
          </TraceLine>
        </TraceCollapsibleTrigger>
        <TraceCollapsibleContent>
          <Trace>
            <TraceCollapsible defaultOpen>
              <TraceCollapsibleTrigger>
                <TraceLine>
                  <TraceIcon>
                    <Icon as={FolderIcon} />
                  </TraceIcon>
                  <TraceTitle>settings</TraceTitle>
                </TraceLine>
              </TraceCollapsibleTrigger>
              <TraceCollapsibleContent>
                <Trace>
                  <TraceLine>
                    <TraceIcon>
                      <Icon as={FileIcon} />
                    </TraceIcon>
                    <TraceTitle>page.tsx</TraceTitle>
                    <TraceDescription>+24 −6</TraceDescription>
                  </TraceLine>
                  <TraceLine>
                    <TraceIcon>
                      <Icon as={FileIcon} />
                    </TraceIcon>
                    <TraceTitle>theme-toggle.tsx</TraceTitle>
                    <TraceDescription>+41</TraceDescription>
                  </TraceLine>
                </Trace>
              </TraceCollapsibleContent>
            </TraceCollapsible>
            <TraceLine>
              <TraceIcon>
                <Icon as={FileIcon} />
              </TraceIcon>
              <TraceTitle>layout.tsx</TraceTitle>
              <TraceDescription>+3 −1</TraceDescription>
            </TraceLine>
          </Trace>
        </TraceCollapsibleContent>
      </TraceCollapsible>
    </Trace>
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

struct FileTreeExample: View {
  var body: some View {
    Trace {
      TraceCollapsible(initiallyExpanded: true) {
        TraceCollapsibleTrigger {
          TraceLine {
            TraceIcon { Ph.folder.regular }
            TraceTitle(verbatim: "app")
          }
        }
        TraceCollapsibleContent {
          Trace {
            TraceCollapsible(initiallyExpanded: true) {
              TraceCollapsibleTrigger {
                TraceLine {
                  TraceIcon { Ph.folder.regular }
                  TraceTitle(verbatim: "settings")
                }
              }
              TraceCollapsibleContent {
                Trace {
                  TraceLine {
                    TraceIcon { Ph.file.regular }
                    TraceTitle(verbatim: "page.tsx")
                    TraceDescription(verbatim: "+24 −6")
                  }
                  TraceLine {
                    TraceIcon { Ph.file.regular }
                    TraceTitle(verbatim: "theme-toggle.tsx")
                    TraceDescription(verbatim: "+41")
                  }
                }
              }
            }
            TraceLine {
              TraceIcon { Ph.file.regular }
              TraceTitle(verbatim: "layout.tsx")
              TraceDescription(verbatim: "+3 −1")
            }
          }
        }
      }
    }
  }
}
```

```kotlin
import androidx.compose.runtime.Composable
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.File
import com.adamglin.phosphoricons.regular.Folder
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceCollapsible
import com.esuiss.ui.components.TraceCollapsibleContent
import com.esuiss.ui.components.TraceCollapsibleTrigger
import com.esuiss.ui.components.TraceDescription
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceTitle

@Composable
fun FileTreeAIDefault() {
    Trace {
        TraceCollapsible(initiallyExpanded = true) {
            TraceCollapsibleTrigger {
                TraceLine {
                    TraceIcon { Icon(PhosphorIcons.Regular.Folder, contentDescription = null) }
                    TraceTitle("app")
                }
            }
            TraceCollapsibleContent {
            Trace {
                TraceCollapsible(initiallyExpanded = true) {
                    TraceCollapsibleTrigger {
                        TraceLine {
                            TraceIcon { Icon(PhosphorIcons.Regular.Folder, contentDescription = null) }
                            TraceTitle("settings")
                        }
                    }
                    TraceCollapsibleContent {
                    Trace {
                        TraceLine {
                            TraceIcon { Icon(PhosphorIcons.Regular.File, contentDescription = null) }
                            TraceTitle("page.tsx")
                            TraceDescription("+24 −6")
                        }
                        TraceLine {
                            TraceIcon { Icon(PhosphorIcons.Regular.File, contentDescription = null) }
                            TraceTitle("theme-toggle.tsx")
                            TraceDescription("+41")
                        }
                    }
                    }
                }
                TraceLine {
                    TraceIcon { Icon(PhosphorIcons.Regular.File, contentDescription = null) }
                    TraceTitle("layout.tsx")
                    TraceDescription("+3 −1")
                }
            }
            }
        }
    }
}
```

File Tree on mobile is built from the same parts as on the web: [Trace](https://suiss.app/ui/docs/components/trace). The example above is a static demo; connect it to your agent by rendering the same parts from your messages.

## Installation

**React Native**

```bash title="Terminal"
npx @esuiss/ui@latest add trace
```

**SwiftUI**

```bash title="Terminal"
npx @esuiss/ui@latest add trace --platform swift
```

**Jetpack Compose**

```bash title="Terminal"
npx @esuiss/ui@latest add trace --platform kotlin
```

## API Reference

See [Trace](https://suiss.app/ui/docs/components/trace).

