File upload
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>FileUploadis the root. The value is a list ofFileUploadFile({ id, file?, preview? }): a pickedFile, or only apreviewURL for something already stored. Controlled throughvalue/onValueChange, or uncontrolled fromdefaultValue. What is picked or dropped is checked againstaccept,maxSizeandmaxFiles; the survivors are appended (replaced, when notmultiple) and reported toonAccept, the rest toonRejectwith a reason (type,size,count). Wearsdata-state="idle|dragging|disabled"and thegroup/file-uploadclass, so any child can tint ongroup-data-[state=dragging]/file-upload:.FileUploadInputis the hidden<input type="file">. It stays mounted, which is what lets a phone's camera hand its photo back.FileUploadDropzoneis only the drop target (data-state="idle|dragging"); put aFileUploadTriggerinside for the click, or make the trigger the dropzone withrender={<FileUploadTrigger />}.FileUploadTriggeropens the picker: a button, or the caller's element throughrender.FileUploadListholds the items (data-emptywhen there are none).FileUploadItemis one file, the context its parts read.FileUploadItemPreviewis an<img>of the item (itspreview, or an object URL made from its file and released when it goes). Withrender={<AvatarImage />}the caller's image element gets thatsrc; with children it is those (anext/image, a<video>, a rotated thumbnail).FileUploadItemRemovetakes the item out of the list.
Compositions
Image thumbnail
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.
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>.
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).
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
| Prop | Type | Default | Description |
|---|---|---|---|
accept | string | any | The browser's accept grammar. |
maxSize | number | none | In bytes. |
maxFiles | number | none | Cap on the list when multiple. |
multiple | boolean | false | Append picks rather than replace. |
disabled | boolean | false | Ignores picks and drops; the trigger is disabled. |
value | FileUploadFile[] | — | Controlled list. |
defaultValue | FileUploadFile[] | [] | 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. |