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.
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.Item — ItemPreview 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
| Part | Default Element | Description |
|---|---|---|
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
| Prop | Type | Default | Description |
|---|---|---|---|
value | File[] | — | Controlled list of selected files |
defaultValue | File[] | [] | Initially selected files (uncontrolled) |
onValueChange | (files: File[]) => void | — | Called with the full list whenever the selection changes |
multiple | boolean | false | Allow selecting more than one file |
accept | string | — | accept filter (e.g. image/*,.pdf) for picker and drops |
maxSize | number | — | Max size in bytes for a single file; larger files rejected |
onReject | (rejections: FileRejection[]) => void | — | Called with files rejected by accept or maxSize |
disabled | boolean | false | Disable 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
| Prop | Type | Default | Description |
|---|---|---|---|
file | File | — (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:
| Attribute | Applies To | Description |
|---|---|---|
data-empty | Root | Present when no files are selected |
data-dragging | Dropzone | Present while a valid drag is over the zone |
data-image | Item | Present when the item's file is an image |
data-disabled | Root, Trigger, Dropzone, ItemDelete | Present 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.