FileUpload

A file picker that accepts input from a trigger button or drag-and-drop, from @clerk/headless. It is a headless primitive: it owns the selected-file state, a hidden <input type="file">, accept filtering (applied to both the picker and dropped files), single/multiple modes, and image preview thumbnails, but ships no styles — you bring your own CSS by targeting the data-* attributes each part emits.

Example

The demo below is intentionally unstyled — it renders the raw primitive so you can see its behavior. Click the trigger to open the native picker, or drag image files onto the area; each selected file renders an <img> preview with a remove button. Non-image files render no preview.

or drag images here

Usage

import { FileUpload } from '@clerk/headless/file-upload';

<FileUpload.Root
  accept='image/*'
  multiple
>
  <FileUpload.Dropzone>
    <FileUpload.Trigger>Choose files</FileUpload.Trigger>
    <span>or drag files here</span>
  </FileUpload.Dropzone>

  <FileList />
</FileUpload.Root>;

The selected files live in FileUpload.Root. Read them with the useFileUpload hook and render each one inside a FileUpload.ItemItemPreview renders an <img> for images and nothing otherwise, so it is safe to include unconditionally:

function FileList() {
  const { files } = FileUpload.useFileUpload();
  return (
    <ul>
      {files.map(file => (
        <li key={file.name}>
          <FileUpload.Item file={file}>
            <FileUpload.ItemPreview />
            <span>{file.name}</span>
            <FileUpload.ItemDelete>Remove</FileUpload.ItemDelete>
          </FileUpload.Item>
        </li>
      ))}
    </ul>
  );
}

Single file (avatar)

Omit multiple and a new selection replaces the previous file instead of appending.

<FileUpload.Root accept='image/*'>
  <FileUpload.Trigger>Upload avatar</FileUpload.Trigger>
</FileUpload.Root>

Controlled

const [files, setFiles] = useState<File[]>([]);

<FileUpload.Root
  value={files}
  onValueChange={setFiles}
>
  {/* ... */}
</FileUpload.Root>;

Parts

PartDefault ElementDescription
FileUpload.Root<div>Root wrapper; owns file state and renders the hidden <input>
FileUpload.Trigger<button>Opens the native file picker
FileUpload.Dropzone<div>Drag-and-drop target; filters dropped files by accept
FileUpload.Item<div>Wraps one selected file; provides item context (file, remove)
FileUpload.ItemPreview<img>Image thumbnail for the item's file (renders nothing for non-images)
FileUpload.ItemDelete<button>Removes the item's file

All parts accept a render prop for polymorphic rendering and standard HTML attributes for their default element. Compound parts throw if used outside their parent (FileUpload.* outside Root; Item-scoped parts outside Item).

FileUpload.useFileUpload() is a hook (not a component) that returns { files, addFiles, removeFile, clearFiles, openFilePicker, disabled } for reading the selection and driving custom UI. It must be called inside FileUpload.Root.

Props

FileUpload.Root

PropTypeDefaultDescription
valueFile[]Controlled list of selected files
defaultValueFile[][]Initially selected files (uncontrolled)
onValueChange(files: File[]) => voidCalled with the full list whenever the selection changes
multiplebooleanfalseAllow selecting more than one file
acceptstringaccept filter (e.g. image/*,.pdf) for picker and drops
maxSizenumberMax size in bytes for a single file; larger files rejected
onReject(rejections: FileRejection[]) => voidCalled with files rejected by accept or maxSize
disabledbooleanfalseDisable the trigger, dropzone, and picker

A FileRejection is { file: File; reason: 'accept' | 'size' | 'overflow' }. onReject fires for both the picker and drops, accept is checked before maxSize, and in single-file mode any files beyond the first are reported as 'overflow' rather than dropped silently. Accepted files in the same batch are still added.

FileUpload.Item

PropTypeDefaultDescription
fileFile— (required)The file this item represents

FileUpload.Trigger, FileUpload.Dropzone, FileUpload.ItemPreview, and FileUpload.ItemDelete take no additional props beyond standard HTML attributes for their default element.

Styling

Each part emits data-* attributes you can target with any CSS solution:

AttributeApplies ToDescription
data-emptyRootPresent when no files are selected
data-draggingDropzonePresent while a valid drag is over the zone
data-imageItemPresent when the item's file is an image
data-disabledRoot, Trigger, Dropzone, ItemDeletePresent when disabled
.cl-file-upload-dropzone {
  border: 2px dashed var(--color-border);
  border-radius: 8px;
  padding: 24px;
}
.cl-file-upload-dropzone[data-dragging] {
  border-color: var(--color-accent);
  background: var(--color-accent-subtle);
}

The hidden <input type="file"> is aria-hidden and out of the tab order; the Trigger button is the accessible control and forwards its click to the input. ItemPreview uses the file name as its alt text.