FilesPreview
UI-only file and folder building blocks for composing file surfaces without Cubitt owning your data model.
Overview
Files is a compound surface for composing file and folder UI from consumer-owned data. Cubitt owns the visual treatment, grid/list layout, controlled selection interactions, inline rename mechanics, item rendering, upload affordances, and accessibility details.
Consumers own file schemas, folder hierarchy, navigation, filtering, sorting, URL state, permissions, rename persistence, actions, and upload lifecycle. Folder support is display-only: pass kind: "folder" and handle open/navigation callbacks in your app.
Usage
import {
Files,
type FilesItem,
} from "@tilt-legal/cubitt-components/files";
const items: FilesItem[] = [
{ id: "contracts", kind: "folder", name: "Contracts", itemCount: 4 },
{
id: "contract-1",
kind: "file",
name: "Client Contract.pdf",
mimeType: "application/pdf",
size: 612_000,
createdBy: "Ava Johnson",
updatedAt: "2026-04-18",
},
];
<Files
selectedIds={selectedIds}
onSelectedIdsChange={setSelectedIds}
onRename={renameItem}
selectionMode="multiple"
>
<Files.Grid>
{items.map((item) => (
<Files.Item
item={item}
key={item.id}
onOpen={(openedItem) => {
if (openedItem.kind === "folder") {
navigateToFolder(openedItem.id);
}
}}
/>
))}
</Files.Grid>
</Files>;The root keeps selection controlled and stores only the transient inline rename draft. Collection components only lay out children, so the consuming app decides which items exist, how they are ordered, which view renders, and how a rename mutation updates source data.
Selection clears from Files.Root when the user clicks anything that is not a
selection-preserving surface. Files.Item, Files.Details, Cubitt Toolbar
inside the Files root, Base UI portals, and elements marked with
data-files-selection-preserve preserve the current selection; blank
grid/list/content space clears it. Use data-files-selection-preserve for
external toolbar islands that sit outside the Files root.
| Surface | Selection behavior |
|---|---|
Files.Item | Preserves selection so item clicks can update selection intentionally. |
Files.Details | Preserves selection while users inspect or rename details. |
Toolbar inside Files | Preserves selection while users search, filter, sort, upload, or open action menus. |
| Base UI portals | Preserves selection while users interact with menus, popovers, and dialogs. |
data-files-selection-preserve | Preserves selection for external toolbar/action islands outside the Files root. |
Use canSelect when some items should be navigable but should not enter
selectedIds. For example, a single-file picker can keep files selectable while
folders only activate/open:
<Files
selectedIds={selectedIds}
onSelectedIdsChange={setSelectedIds}
canSelect={(item) => item.kind !== "folder"}
selectionBehavior="toggle"
selectionMode="single"
>
{/* collections */}
</Files>For explorer-style surfaces, leave folders selectable and clear selection in
your folder navigation handler. For picker-style surfaces, use canSelect only
when a folder should be navigable without becoming a selected value.
For right-click menus, compose the ContextMenu components with
useFilesContextMenu so your app owns menu actions while Cubitt keeps item
selection semantics consistent.
Files.Item
Contract
FilesItem is the display contract shared by Files.Item, Files.Grid,
Files.List, and Files.Details.
type FilesItem = {
id: string;
kind: "file" | "folder";
name: string;
size?: number;
itemCount?: number;
mimeType?: string;
createdBy?: ReactNode;
createdAt?: Date | number | string;
updatedAt?: Date | number | string;
badge?: ReactNode;
icon?: ReactNode;
status?:
| { type: "loading" }
| { type: "uploading"; progress?: number }
| { type: "error"; message?: ReactNode };
disabled?: boolean;
};Use size for file byte size. Cubitt formats it as KB, MB, or GB. Use
itemCount for folders; it renders in the Size column as an item count rather
than bytes.
Use mimeType for file type and icon derivation. If icon is provided, it
overrides the derived file/folder icon.
Use createdBy, createdAt, and updatedAt for first-class list/details
metadata. Files.List keeps configured columns stable when values are missing;
the default headers are Name, Type, Size, Created by, Created, and Updated.
Consumers can still extend FilesItem with app-owned fields:
type MatterFileItem = FilesItem & {
matterId: string;
parentId: string | null;
workflowStatus: "Draft" | "Reviewed" | "Filed";
};Use status: { type: "loading" } for skeleton placeholders,
status: { type: "uploading", progress } for upload display, and
status: { type: "error", message } for failed upload display. These statuses
are visual only; the consuming app still owns retry, cancel, persistence, and
upload queue state. Uploading items with missing or zero progress use only the
indeterminate icon. Positive progress and failures render after a middle dot in
the list Name cell.
Rendering
Files.Item renders one display-only FilesItem. Grid and list rows are
owned by Cubitt and use the item data to render selection, icon, name, size,
type, metadata, badge, and status affordances.
Use badge for small icon indicators, such as shared, reviewed, or
confidential states.
Use Files.FolderIcon or the named FilesFolderIcon export when surrounding
file UI, such as toolbar breadcrumbs, should use the same custom folder artwork
as Files.Item and Files.Details.
API Reference
Files
Root provider for controlled selection and shared item interaction state.
| Prop | Type | Default | Description |
|---|---|---|---|
items | FilesItem[] | [] | Optional item data map for root-level features such as Files.Details resolving selected IDs without relying on mounted item registration. |
detailsItem | FilesItem | null | null | Item used by Files.Details when nothing is selected, usually the current directory. |
metadataProperties | FilesMetadataProperty[] | all | Metadata properties shown by default list columns and Files.Details.Metadata. Use a subset to hide properties such as size without altering item data. |
selectedIds | string[] | [] | Controlled selected item ids. |
onSelectedIdsChange | (ids: string[]) => void | - | Called when item interaction commits a selection change. |
selectionMode | "none" | "single" | "multiple" | "multiple" | Selection mode for item interactions and selection affordances. |
selectionBehavior | "replace" | "toggle" | "replace" | Whether normal clicks replace selection or toggle items. Modifier and range selection are still handled by Files.Item. |
canSelect | (item) => boolean | - | Optional per-item predicate for items that can enter selectedIds. Use it for navigation-only folders in single-file pickers. |
disabled | boolean | false | Disables item interaction for the whole surface. |
onRename | (item, name) => Promise<void> | void | - | Enables inline item rename. Press Enter on a single selected item to edit its name; blur or Enter submits. The consuming app updates item data. |
canRename | (item) => boolean | - | Optional per-item predicate for disabling inline rename while keeping other item interactions available. |
onMove | (event) => Promise<void> | void | - | Enables drag-and-drop moves. Receives the active item, all moved file/folder items, and the folder or collection destination. |
canMove | (event) => boolean | - | Optional predicate for preventing invalid moves, such as moving a folder into itself or one of its descendants. |
render | ReactNode | - | Composes the Files root onto another element or component, such as DialogContent, without adding a wrapper element. |
className | string | - | Overrides or extends the root layout. |
children | ReactNode | - | Files collections, content, details, and surrounding controls. |
Also accepts standard div props through the default root.
Files.Content
| Prop | Type | Default | Description |
|---|---|---|---|
moveDestinationId | string | null | null | Id emitted when items are dropped onto the content area rather than a folder, usually the directory id. |
className | string | - | Overrides or extends the flexible content wrapper. |
children | ReactNode | - | Files collection, empty state, or custom content. |
Also accepts standard div props.
Files.Item
Files.Item must be rendered inside Files.Grid or Files.List so it can
inherit the active layout.
| Prop | Type | Default | Description |
|---|---|---|---|
item | FilesItem | - | Required display data. |
onOpen | (item, event) => void | - | Called on double click, or Enter when inline rename is unavailable. Use this for file open and folder navigation. |
disabled | boolean | false | Disables the item in addition to root and item-level disabled state. |
className | string | - | Overrides or extends the item root styles. |
Also accepts standard button props.
Files.UploadButton
Stateless file picker action. It does not create upload queues or persist files.
| Prop | Type | Default | Description |
|---|---|---|---|
accept | string | readonly string[] | - | Accepted MIME patterns or extensions. |
directory | boolean | false | Enables folder selection where the browser supports directory inputs. |
maxFiles | number | - | Maximum accepted files before excess entries are rejected. |
maxSizeMB | number | null | null | Maximum accepted file size in megabytes. |
multiple | boolean | true | Allows multiple file selection. |
onFilesAccepted | (entries: FilesUploadEntry[]) => void | - | Called with accepted file entries. |
onFilesRejected | (entries: FilesRejectedEntry[]) => void | - | Called with rejected file entries. |
children | ReactNode | Upload | Custom button content. |
disabled | boolean | false | Disables the button and hidden file input. |
Also accepts Button props except onChange and onClick.
Types And Hooks
| Export | Purpose |
|---|---|
FilesItem | Display-only file/folder item data. |
FilesMetadataProperty | Metadata property id accepted by Files.metadataProperties. |
FilesCanSelect | Per-item predicate used by Files to keep navigation-only items out of selectedIds. |
FilesViewMode | "grid" | "list". Useful for consumer-owned view state. |
FilesSelectionMode | "none" | "single" | "multiple". |
FilesUploadEntry | Accepted file entry emitted by Files.UploadButton. |
FilesRejectedEntry | Rejected file entry emitted by Files.UploadButton. |
FilesFolderIcon | Custom folder SVG used by Files items/details and reusable in surrounding file UI. |
useFilesContext | Optional custom-slot hook for root selection state and inline rename actions. |
useFilesContextMenu | Optional helper for consumer-owned context menus that need Files item-selection semantics. |
useFilesItemContext | Optional custom-slot hook for the current item. |
Components
Grid View
Grid layout for consumer-owned item children.
List View
Table-like rows for consumer-owned item children.
Details
Presentational details surface with item-aware slots.
Examples
Details Panel
Build a file browser with a consumer-owned details sidebar, search, view switching, and inline rename.
Attachment Picker
Build a reusable file attachment picker from Dialog, Toolbar, Files, and primitive components.
Generate Transcript Picker
Build a single-file transcript generation picker with provider selection and async confirmation.
Context Menu
Build a consumer-owned right-click menu with file, folder, and multi-selection rules.