File Browser

Let the user navigate a folder tree from their storage and pick files, with list and grid views, sorting and full keyboard support.

Pattern

≈ 1,799 tokens per use (tool 886 · guidance 287 · typical payload 626)How token costs are estimated

Example: File Browser

Attach the Q3 board deck so you can pull the revenue slides.

Attach the Q3 board deck

Acme shared drive

View

Arrow keys move between items. Enter opens a folder, Space selects a file. Backspace goes up a folder.

Name
Open
Q2 board deck, Presentation, shared
Q2 board deck, PDF
Q3 board deck, Presentation, shared
Q3 board deck (draft v2), Presentation
Q3 board minutes, Document
Q3 financials appendix, Spreadsheet, shared

Choose up to 3

When to use

  • The user needs to find and pick files in their storage: attach a deck, choose contracts to summarize.
  • The agent listed a folder or searched, and found several candidates in different folders.
  • The user wants to look around a folder the agent opened, with links to open each file.

When not to use

  • Only one file matches. Use it and say which one.
  • The items aren't files, e.g. accounts or people. Use the Entity Picker.
  • The user wants to upload files from their device. Use the File Upload.
  • The agent hasn't listed the folder yet. List it first; never describe files from memory.

How the agent should use it

  • Every folder and file comes from a tool result, with its real id, name and modifiedAt.
  • Send only the part of the tree around the task, a few levels deep.
  • Set startFolderId to the folder with the likely match. The path above it stays navigable.
  • Use selection: "single" when the next step takes one file, and max when it takes a few. Use "none" for a read-only browse with Open links.
  • Only include url when the tool returned one. Anything but http(s) is dropped.
  • Act only on the files returned with select.

Interaction

InputAction
Click or Enter on a folderOpens it. The new folder and its item count are announced
Click, Space or Enter on a fileSelects it (toggles when multiple)
↑ ↓ Home EndMove through the items (by row in grid view)
→ ←Move to and from a file's Open link; in grid view, also between items
Backspace or Alt+↑Goes up to the parent folder
BreadcrumbJumps to any folder above the current one

The folder is an ARIA grid that keeps focus and tracks the active cell with aria-activedescendant, so each row can hold a real Open link. Folders always sort first. The selection persists across folders and shows as removable chips in the footer; once max is reached, other files are disabled. Folders with more than 6 items scroll inside the card. Each item shows its type in a coloured box (folders as a folder shape), and the Open link appears on hover or when the row is active. Folder changes and selection counts are announced in a live region.

Installation

npx shadcn@latest add https://your-site.com/r/file-browser.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { fileBrowserTool } from "@/components/genui/file-browser.schema"

const tools = {
  // No execute(): the call is completed by the user's choice in the UI.
  [fileBrowserTool.name]: tool({
    description: fileBrowserTool.description,
    inputSchema: fileBrowserTool.inputSchema,
  }),
}
components/message.tsx
import { FileBrowser } from "@/components/genui/file-browser"

function ToolPart({ part, addToolResult }) {
  if (part.type !== "tool-browse_files") return null

  return (
    <FileBrowser
      {...part.input}
      status={part.state === "input-streaming" ? "streaming" : undefined}
      result={part.state === "output-available" ? part.output : undefined}
      onResult={(result) =>
        addToolResult({
          tool: "browse_files",
          toolCallId: part.toolCallId,
          output: result,
        })
      }
    />
  )
}

With selection: "none" nothing is sent back, so give the tool an execute that returns immediately. Otherwise the agent receives one of:

{ action: "select", files: [{ id: "f_q3_deck", name: "Q3 board deck", kind: "slides" }] }
{ action: "cancel" }

With LangGraph

Push it from a graph node with ui.push({ name: "browse_files", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

Pass the stored result back as result. The browser lists the attached files (or the cancelled state) and never calls onResult again. The result carries each file's name and kind, so the tree isn't needed.

<FileBrowser
  {...part.input}
  result={part.state === "output-available" ? part.output : undefined}
/>

Schema

PropTypeNotes
titlestring?What the user is doing: "Attach the Q3 board deck"
source{ label }?Data-bound. Where the files live: "Acme shared drive"
rootFolderNodeData-bound. The tree, starting at the folder the agent opened
startFolderIdstring?Folder to open first. Default: root
selection"none" | "single" | "multiple"Default "multiple"
maxnumber?Most files, when multiple
submitLabelstring?Verb for the confirm button; the count is added: "Attach 2 files". Default "Select"

FolderNode is { type: "folder", id, name, children: (FolderNode | FileNode)[] }, up to 200 children each.

FileNode is { type: "file", id, name, kind, sizeBytes?, modifiedAt, owner?, url?, shared? }, where kind is one of doc, sheet, slides, pdf, image, video, archive or other, and modifiedAt is ISO 8601.

Host props: status (streaming, ready, submitting, selected, cancelled), onResult, result and className.

Source

On this page