v1.0

File Upload

Drag-and-drop upload area with a per-file list that shows file format, progress, and upload status.

Pro

Drag and drop file(s)

Max 2.4 GB eachOnly ZIP, PDF, AI, CDR, CSV, JPG or MP4
MP4

product-demo.mp4

Uploading
0%
0 MB of 24 MB

Description

FileUpload is a compound component for uploading files. FileUpload owns the file list and the upload lifecycle; the drag-and-drop target is the free Dropzone (with a built-in trigger), and FileUploadList renders one FileUploadItem per file showing its format icon, name, transfer progress, and status (queued, uploading, paused, completed, or error).

It is managed by default: you pass an onUpload that performs the transfer and reports progress, and the component tracks files, validation, pause/resume, retry, and removal for you. Drop in a FileUploadList with no children and it renders every file automatically; compose FileUploadItem children yourself when you need a custom row. For fully custom UIs, the headless useFileUpload hook exposes the same state and actions.

Reach for it whenever users attach files: document uploads, image and media galleries, and attachment fields. For a single hidden file input with no list or progress, a plain Field with a native input is enough.

FileUpload is a Create UI Pro component. With a Pro seat, npx @create-ui/cli add file-upload installs it. The drop area is the standalone Dropzone — a free component FileUpload composes — and installing file-upload pulls it in automatically. The previews below are marked with a Pro badge.

Installation

pnpm dlx @create-ui/cli add file-upload

Anatomy

FileUpload provides the upload context to its parts. Render a Dropzone and the list inside it; the list renders a row per file unless you pass your own FileUploadItem children.

<FileUpload onUpload={...}>
  <Dropzone />
  <FileUploadList />
</FileUpload>

For a custom row, compose the item parts and omit the ones you don't want:

<FileUploadItem file={file}>
  <FileUploadItemMain>
    <FileUploadItemIcon />
    <FileUploadItemContent>
      <FileUploadItemHeader>
        <FileUploadItemName />
      </FileUploadItemHeader>
      <FileUploadItemFooter>
        <FileUploadItemStatus />
        <FileUploadItemMeta />
      </FileUploadItemFooter>
    </FileUploadItemContent>
  </FileUploadItemMain>
  <FileUploadItemProgress />
</FileUploadItem>

Usage

import { Dropzone } from "@/components/ui/dropzone"
import { FileUpload, FileUploadList } from "@/components/ui/file-upload"
<FileUpload
  multiple
  accept=".pdf,.zip,image/*"
  maxSize={25 * 1024 * 1024}
  onUpload={(files, { onProgress, onSuccess, onError }) => {
    for (const file of files) {
      // POST file.file to your server; report progress as it uploads,
      // then resolve the row:
      onProgress(file.id, 50)
      onSuccess(file.id)
    }
  }}
>
  <Dropzone />
  <FileUploadList />
</FileUpload>

Without an onUpload, accepted files stay in the queued state. accept, maxSize, and maxFiles reject non-matching files automatically and surface a message.

Examples

Sizes

The file list has its own size on FileUpload — sm (a single dense row per file) or lg (the default two-line row). It is set on the root and cascades to every FileUploadItem. The drop-area size is a separate axis on the Dropzone.

Pro
PDF

annual-report.pdf

Uploading
64%
1.5 MB of 2.3 MB
PNG

logo.png

Completed
480 KB
PDF

annual-report.pdf

64%
PNG

logo.png

Upload states

Each row reflects its file's status: queued, uploading and paused (with a progress meter), completed (with a check), or error (with the rejection message). Pass file objects to FileUploadItem to render any status directly.

Pro
PDF

annual-report.pdf

Uploading
16%
377 KB of 2.3 MB
ZIP

design-assets.zip

Queued
0 MB of 12 MB
MP4

promo-video.mp4

Paused
45%
3.6 MB of 8 MB
PNG

logo.png

Completed
480 KB
ZIP

huge-archive.zip

File exceeds the 25MB limit
30 MB

Completed actions

Set completedActions to reveal Change and Download buttons once a file finishes. Change re-picks a file and replaces it in place; Download saves it. At size="sm" they render as inline icon buttons instead of a bar. Override either with onReplace or onDownload.

Pro

Drag and drop file(s)

Max 2.4 GB eachOnly PDF, ZIP, JPG or MP4
PDF

brand-guidelines.pdf

Completed
4.2 MB

Drag and drop file(s)

PDF

brand-guidelines.pdf

Trigger only

Render a DropzoneTrigger on its own — a single button that opens the picker and feeds the same managed list, with no drop area.

Pro

Accessibility

Each row action is a real <button> with an aria-label. The drop area's own click, keyboard, and aria behaviour is documented on the Dropzone page.

KeyDescription
TabMoves focus to each row's action buttons.
Space EnterActivates the focused button (pause/resume, remove, ...).

ARIA notes:

  • Status changes are announced through a built-in aria-live region — polite for completed and removed files, assertive for errors — so screen-reader users hear upload outcomes.
  • Removing a row moves focus to an adjacent row's controls (or the drop-area trigger), never to <body>.
  • Each action button carries an aria-label (Pause, Resume, Remove, Change, Download, Retry).
  • Animations respect prefers-reduced-motion; the meter and status morphs collapse to instant state changes.

Styling

Tailwind override: pass className to merge Tailwind classes with the component's CVA classes (via cn()):

<FileUpload className="max-w-md">
  <Dropzone />
  <FileUploadList />
</FileUpload>

Data slots and attributes: the component sets these for CSS targeting.

  • data-slot="file-upload" on the root.
  • data-slot="file-upload-list" on the list wrapper.
  • data-slot="file-upload-item" with data-status="<status>" and data-size on each row.
  • data-slot="file-upload-item-name", -icon, -status, -meta, -progress, -actions, -background on the row parts.

The drop area's slots (dropzone, dropzone-trigger, ...) live on the Dropzone reference.

Target a specific status in CSS:

[data-slot="file-upload-item"][data-status="error"] {
  /* ... */
}
  • Dropzone: the free drag-and-drop target FileUpload composes for its drop area.
  • Progress: the line and circular meters used for per-file upload progress.
  • File Format: the file-type glyph rendered as each row's icon.
  • Button: the trigger and row action buttons.

API Reference

FileUpload

Root that owns the file list and upload lifecycle and provides context to every part. Renders a <div data-slot="file-upload"> and extends React.ComponentProps<"div"> minus the hook option props listed below.

Props

PropTypeDefaultDescription
multiplebooleanfalseAllow selecting more than one file.
acceptstring-Comma-separated extensions or MIME types to allow; mismatches reject.
maxSizenumber-Max bytes per file; larger files are rejected.
maxFilesnumber-Cap on the total number of files.
disabledbooleanfalseDisables the dropzone, trigger, and row actions.
onUpload(files, handlers) => void | Promise<void>-Performs the transfer; call handlers.onProgress / onSuccess / onError.
completedActionsbooleanfalseShow built-in Change and Download buttons on completed rows.
onReplace(file: UploadFile) => void-Override the Change action.
onDownload(file: UploadFile) => void-Override the Download action.
onFileAccept(file: UploadFile) => void-Fires when a file passes validation.
onFileReject(file: File, reason: string) => void-Fires when a file is rejected, with the reason.
onRemove(file: UploadFile) => void-Fires when a row is removed.
onPause(file: UploadFile) => void-Fires when a row is paused; abort your request here.
onResume(file: UploadFile) => void-Fires when a row is resumed; continue from the kept progress.
defaultFilesUploadFile[]-Uncontrolled initial list.
filesUploadFile[]-Controlled list.
onFilesChange(files: UploadFile[]) => void-Fires whenever the list changes.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The dropzone, trigger, and list.

Variants

VariantOptionsDefaultDescription
size"sm" "lg""lg"Row density of the file list; cascades to every FileUploadItem.

Dropzone

The drop area is the free Dropzone component, composed inside FileUpload. When nested it inherits accept, maxSize, multiple, and disabled from the root and feeds accepted files straight into the list, so no wiring is needed. Its size (sm, md, lg) is independent of the list size. Use DropzoneTrigger on its own inside FileUpload for a picker button with no drop area. See the Dropzone reference for its props, subparts, sizes, and the useDropzone hook.

FileUploadList

Renders the rows. Renders a <div data-slot="file-upload-list"> and extends Omit<React.ComponentProps<"div">, "children">.

Props

PropTypeDefaultDescription
childrenReact.ReactNode | (file: UploadFile) => ReactNode-Omit for the managed list (a row per file); pass a render function or FileUploadItem children for control.
classNamestring-Tailwind classes merged via cn().

FileUploadItem

A single file row. Renders a <div data-slot="file-upload-item" data-status data-size> and extends Omit<React.ComponentProps<"div">, "children">.

Props

PropTypeDefaultDescription
fileUploadFile-The file to render; supplies name, format, size, status, progress.
statusFileUploadStatus"queued"Status used when no file is given (manual composition).
valuenumber0Progress 0–100 used when no file is given.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Custom item parts; omit for the default row.

Variants

VariantOptionsDefaultDescription
size"sm" "lg"-Row density; inherits from FileUpload when unset.

useFileUpload

The headless hook behind FileUpload, for fully custom UIs. Accepts the same options FileUpload forwards and returns the file list plus actions. The drag, drop, and hidden-input handling live in the free useDropzone hook, which feeds accepted files in through onFilesAccepted.

import { useFileUpload } from "@/hooks/use-file-upload"

Options

OptionTypeDefaultDescription
multiplebooleanfalseAllow more than one file.
acceptstring-Allowed extensions or MIME types.
maxSizenumber-Max bytes per file.
maxFilesnumber-Cap on the number of files.
disabledbooleanfalsePrevent adding or replacing files.
onUpload(files, handlers) => void | Promise<void>-Perform the transfer and report progress.
defaultFilesUploadFile[]-Uncontrolled initial list.
filesUploadFile[]-Controlled list.
onFilesChange(files: UploadFile[]) => void-Fires when the list changes.
onFileAccept(file: UploadFile) => void-Fires when a file passes validation.
onFileReject(file: File, reason: string) => void-Fires when a file is rejected.
onRemove(file: UploadFile) => void-Fires when a file is removed.
onPause(file: UploadFile) => void-Fires on pause.
onResume(file: UploadFile) => void-Fires on resume.

Returns

FieldTypeDescription
filesUploadFile[]The current file list.
rejectionMessagestring | nullThe latest rejection reason, if any.
addFiles(files: FileList | File[]) => voidValidate and add files programmatically.
onFilesAccepted(files: File[]) => voidAdd already-validated files (used by the dropzone).
removeFile retryFile pauseFile resumeFile(id: string) => voidRow actions by file id.
replaceFile(id: string) => voidRe-pick and swap a file in place.
clearFiles clearRejection() => voidClear the list / the rejection message.

Types

type FileUploadStatus =
  | "queued"
  | "uploading"
  | "paused"
  | "completed"
  | "error"
 
type UploadFile = {
  id: string
  file?: File
  name: string
  size: number
  status: FileUploadStatus
  progress: number
  error?: string
  format: string
  url?: string
}
 
type UploadHandlers = {
  onProgress: (id: string, progress: number) => void
  onSuccess: (id: string) => void
  onError: (id: string, error: string) => void
}

Composition parts

Lower-level slots for custom rows. Each renders a styled <div> (or <p>) with a data-slot and merges className via cn(). For the drop area's parts (DropzoneHeader, DropzoneIcon, DropzoneTitle, and so on), see the Dropzone reference.

PartRendersPurpose
FileUploadItemMaindivThe icon + content row inside an item.
FileUploadItemIcondivFile-format glyph (built on FileFormat).
FileUploadItemContentdivName header + footer column.
FileUploadItemHeaderdivName + inline actions row.
FileUploadItemNamepTruncated file name.
FileUploadItemActionsdivTrailing action-button cluster.
FileUploadItemFooterdivStatus + meta row.
FileUploadItemStatusdivStatus label with meter or state icon.
FileUploadItemMetadivLoaded / total byte readout.
FileUploadItemProgress-The line progress bar (built on Progress).
FileUploadItemBackgrounddivThe animated fill behind an uploading row.