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.
≈ 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
Arrow keys move between items. Enter opens a folder, Space selects a file. Backspace goes up a folder.
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,nameandmodifiedAt. - Send only the part of the tree around the task, a few levels deep.
- Set
startFolderIdto the folder with the likely match. The path above it stays navigable. - Use
selection: "single"when the next step takes one file, andmaxwhen it takes a few. Use"none"for a read-only browse with Open links. - Only include
urlwhen the tool returned one. Anything but http(s) is dropped. - Act only on the files returned with
select.
Interaction
| Input | Action |
|---|---|
| Click or Enter on a folder | Opens it. The new folder and its item count are announced |
| Click, Space or Enter on a file | Selects it (toggles when multiple) |
| ↑ ↓ Home End | Move 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 |
| Breadcrumb | Jumps 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
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,
}),
}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
| Prop | Type | Notes |
|---|---|---|
title | string? | What the user is doing: "Attach the Q3 board deck" |
source | { label }? | Data-bound. Where the files live: "Acme shared drive" |
root | FolderNode | Data-bound. The tree, starting at the folder the agent opened |
startFolderId | string? | Folder to open first. Default: root |
selection | "none" | "single" | "multiple" | Default "multiple" |
max | number? | Most files, when multiple |
submitLabel | string? | 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.