Files
Preview

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 slots, 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" },
  {
    id: "contract-1",
    kind: "file",
    name: "Client Contract.pdf",
    mediaType: "application/pdf",
    sizeLabel: "612 KB",
  },
];

<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.

SurfaceSelection behavior
Files.ItemPreserves selection so item clicks can update selection intentionally.
Files.DetailsPreserves selection while users inspect or rename details.
Toolbar inside FilesPreserves selection while users search, filter, sort, upload, or open action menus.
Base UI portalsPreserves selection while users interact with menus, popovers, and dialogs.
data-files-selection-preservePreserves 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, Files.Details, and custom item slots.

type FilesItem<TSource = unknown> = {
  id: string;
  kind: "file" | "folder";
  name: string;
  size?: number;
  mediaType?: string;
  sizeLabel?: string;
  description?: string;
  badge?: ReactNode;
  icon?: ReactNode;
  thumbnailUrl?: string;
  state?: "default" | "loading" | "uploading" | "error";
  progress?: number;
  disabled?: boolean;
  source?: TSource;
};

source is a passthrough for consumer data. Cubitt does not read it. Use size when Cubitt should calculate multi-file details totals; use sizeLabel when the consuming app already has a formatted display value.

Use state: "loading" for skeleton placeholders, state: "uploading" with progress for upload display, and state: "error" for failed upload display. These states are visual only; the consuming app still owns retry, cancel, persistence, and upload queue state.

Composition

Files.Item renders one display-only FilesItem. Default grid and list rendering is built from the same item slots consumers can use when composing custom item content.

The same item context powers the slots used by grid rows, list rows, and details surfaces.

SlotDefault
Files.ItemSelectionSelection check indicator. Hidden when selectionMode="none" or canSelect returns false for the item.
Files.ItemIconFolder icon, image icon, file icon, item.icon, or item.thumbnailUrl.
Files.ItemNameTruncated item.name with a title attribute, or inline rename textarea.
Files.ItemMetaitem.description and item.sizeLabel.
Files.ItemBadgeitem.badge rendered as the compact icon indicator used in grid and list items.
Files.ItemProgressUpload/error status text for the item.

Use badge for small icon indicators, such as shared, reviewed, or confidential states. If an app needs explanatory labels, keep that label in app-owned data and compose a custom Files.ItemBadge slot with the right accessible label or tooltip.

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.

PropTypeDefaultDescription
itemsFilesItem[][]Optional item data map for root-level features such as Files.Details resolving selected IDs without relying on mounted item registration.
detailsItemFilesItem | nullnullItem used by Files.Details when nothing is selected, usually the current directory.
selectedIdsstring[][]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.
disabledbooleanfalseDisables 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.
classNamestring-Overrides or extends the root layout.
childrenReactNode-Files collections, content, details, or custom composition.

Also accepts standard div props.

Files.Content

PropTypeDefaultDescription
moveDestinationIdstring | nullnullId emitted when items are dropped onto the content area rather than a folder, usually the directory id.
classNamestring-Overrides or extends the flexible content wrapper.
childrenReactNode-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.

PropTypeDefaultDescription
itemFilesItem<TSource>-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.
disabledbooleanfalseDisables the item in addition to root and item-level disabled state.
childrenReactNode-Custom item content composed from item slots or app-specific cells.
classNamestring-Overrides or extends the item root styles.

Also accepts standard button props.

Files.Item Slots

ExportPropsDescription
Files.ItemSelectionComponentProps<"span">Selection check indicator. Hidden when selection is disabled for the item.
Files.ItemIconComponentProps<"div">Folder icon, image icon, file icon, item.icon, or item.thumbnailUrl.
Files.ItemNameComponentProps<"span">Truncated item.name, or inline rename textarea for the active item.
Files.ItemMetaComponentProps<"span">item.description and item.sizeLabel.
Files.ItemBadgeComponentProps<"span">Compact badge indicator from item.badge or custom children.
Files.ItemProgressComponentProps<"span">Upload/error status text for the item.

Files.UploadButton

Stateless file picker action. It does not create upload queues or persist files.

PropTypeDefaultDescription
acceptstring | readonly string[]-Accepted MIME patterns or extensions.
directorybooleanfalseEnables folder selection where the browser supports directory inputs.
maxFilesnumber-Maximum accepted files before excess entries are rejected.
maxSizeMBnumber | nullnullMaximum accepted file size in megabytes.
multiplebooleantrueAllows multiple file selection.
onFilesAccepted(entries: FilesUploadEntry[]) => void-Called with accepted file entries.
onFilesRejected(entries: FilesRejectedEntry[]) => void-Called with rejected file entries.
childrenReactNodeUploadCustom button content.
disabledbooleanfalseDisables the button and hidden file input.

Also accepts Button props except onChange and onClick.

Types And Hooks

ExportPurpose
FilesItemDisplay-only file/folder item data.
FilesCanSelectPer-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".
FilesUploadEntryAccepted file entry emitted by Files.UploadButton.
FilesRejectedEntryRejected file entry emitted by Files.UploadButton.
FilesFolderIconCustom folder SVG used by Files items/details and reusable in surrounding file UI.
useFilesContextOptional custom-slot hook for root selection state and inline rename actions.
useFilesContextMenuOptional helper for consumer-owned context menus that need Files item-selection semantics.
useFilesItemContextOptional custom-slot hook for the current item.

Components

Examples

On this page