File upload

Inspect

Picking files is a compound, the way a Base UI component is: FileUpload is the root and context, and the parts are the pieces every picker is made of. The four pickers below are compositions of them.

Anatomy

<FileUpload
  accept="image/*"
  maxSize={5 * MB}
  value={files}
  onValueChange={setFiles}
>
  <FileUploadInput /> {/* the persistent hidden input */}
  <FileUploadDropzone>
    {" "}
    {/* the drop target */}
    <FileUploadTrigger render={<Button />}>
      {/* opens the picker */}
      Choose a photo
    </FileUploadTrigger>
  </FileUploadDropzone>
  <FileUploadList>
    {files.map((file) => (
      <FileUploadItem key={file.id} value={file}>
        <FileUploadItemPreview /> {/* an <img>; children for your own media */}
        <FileUploadItemRemove />
      </FileUploadItem>
    ))}
  </FileUploadList>
</FileUpload>
  • FileUpload is the root. The value is a list of FileUploadFile ({ id, file?, preview? }): a picked File, or only a preview URL for something already stored. Controlled through value / onValueChange, or uncontrolled from defaultValue. What is picked or dropped is checked against accept, maxSize and maxFiles; the survivors are appended (replaced, when not multiple) and reported to onAccept, the rest to onReject with a reason (type, size, count). Wears data-state="idle|dragging|disabled" and the group/file-upload class, so any child can tint on group-data-[state=dragging]/file-upload:.
  • FileUploadInput is the hidden <input type="file">. It stays mounted, which is what lets a phone's camera hand its photo back.
  • FileUploadDropzone is only the drop target (data-state="idle|dragging"); put a FileUploadTrigger inside for the click, or make the trigger the dropzone with render={<FileUploadTrigger />}.
  • FileUploadTrigger opens the picker: a button, or the caller's element through render.
  • FileUploadList holds the items (data-empty when there are none).
  • FileUploadItem is one file, the context its parts read.
  • FileUploadItemPreview is an <img> of the item (its preview, or an object URL made from its file and released when it goes). With render={<AvatarImage />} the caller's image element gets that src; with children it is those (a next/image, a <video>, a rotated thumbnail).
  • FileUploadItemRemove takes the item out of the list.

Compositions

Image thumbnail

Inspect

Avatar upload

ImageUploadSingleAvatar: the avatar is the drop target and the trigger, a remove button rides its corner. Single file, images only; onImageChange gets the File or null.

Inspect

Single image

SingleImageDropzone: a square picker whose value is a URL the caller owns. Pick or drop a file and onFileSelect gets it; the caller uploads and hands back imageUrl. onRemove adds the remove button; isUploading / isGenerating draw the busy overlay. A URL ending in a video extension plays as a <video>.

Inspect

Multiple sortable

ImageUploadMultipleSortable: the listing form's photo grid. The images are the caller's (previews, optional imageIds, displayPreviews, rotations, nsfwFlags, uploadingFlags); the grid reports onFilesSelected, onRemove (by index) and onReorder (the new ids), with onEdit per thumbnail (rotating is in the editor). The first thumbnail wears the Cover badge, and the whole grid is the drop target. Ordering is the Sortable compound (dnd-kit).

Inspect

Multiple, no ordering

ImageUploadMultipleDefault: a big dropzone while empty, then a grid of thumbnails with an "add more" cell, for bulk work where order does not matter. The root's value / defaultValue / onValueChange, accept, maxFiles and maxSize, plus the empty state's copy (uploadTitle, uploadDescription, uploadHint).

FileUpload props

PropTypeDefaultDescription
acceptstringanyThe browser's accept grammar.
maxSizenumbernoneIn bytes.
maxFilesnumbernoneCap on the list when multiple.
multiplebooleanfalseAppend picks rather than replace.
disabledbooleanfalseIgnores picks and drops; the trigger is disabled.
valueFileUploadFile[]—Controlled list.
defaultValueFileUploadFile[][]Uncontrolled start.
onValueChange(value: FileUploadFile[]) => void—The new list.
onAccept(files: File[]) => void—The files that passed, in the order picked.
onReject(rejections: FileUploadRejection[]) => void—The files that did not, with reason.