The slotsmith FileUploader is a React file uploader. Drop or browse files, check them against your limits, preview images, and upload them with progress, retry and cancellation. Leave out the upload function and it is a picker that never touches the network. Every part is a slot you can replace.

Overview

The demo below uses the fallbacks only, and its uploads are simulated in the page: nothing leaves your browser. Drop up to four images or PDFs, or press Enter on the drop zone to browse. Tick the box to make the next uploads fail, then retry them.

Drag and drop, or browseimage/*, .pdf · up to 5 MB · max 4 files

No files yet.

Each feature has a guide with its own live demo: the picker for forms that send files themselves, uploads with progress, cancel and retry, the tile and compact layouts, editing a record that already has files, validation, and virtual lists for batches of hundreds.

Install

npm i slotsmith

VirtualFileUploader from slotsmith/virtual also needs @tanstack/react-virtual.

import "slotsmith/file-uploader.css";

Import the stylesheet once, at your app's entry. slotsmith/styles.css holds every component's styles instead.

Quick start

Render FileUploader and read the files from onFilesChange. With no upload prop nothing is sent. accept filters the file dialog and is checked again on drop, and maxSize is in bytes.

Drag and drop, or browseimage/*, .pdf · up to 5 MB

No files picked.

Guides

  • The pickerUse the slotsmith FileUploader as a file picker: leave out upload, read the File objects from onFilesChange, and send them with your form.
  • UploadsGive the slotsmith FileUploader an upload function: files are queued with progress, cancel through an AbortSignal, and retry after a failure.
  • The tile layoutThe slotsmith FileUploader's tile variant: one square where the picked image replaces the drop zone, for avatars, logos and covers.
  • The compact layoutThe slotsmith FileUploader's compact variant: a Browse button and the limits on one line, with the files listed under it, for tight forms and toolbars.
  • Editing a recordShow the files a record already has in the slotsmith FileUploader: pass their URLs as defaultValue, then save the URLs onUrlsChange gives back.
  • ValidationLimit what the slotsmith FileUploader accepts with accept, maxSize, minSize, maxFiles and your own validate rule; refused files are explained and announced.
  • Virtual listsPass virtual to the slotsmith FileUploader for batches of hundreds of files: only the rows in view are rendered, and the rest works as before.

Reference

The API page is generated from the library’s source, so it lists every slot, prop and label the installed version has. The file uploader has two label sections: the parts’ text, and the validation messages for refused files.

FileUploader is also a set of compound parts, for a layout of your own. FileUploader.Provider runs the uploader and renders no markup; the others render the parts in it, and useFileUploaderContext() reads its state anywhere inside. The validation guide’s demo is built from them. The list is read from the installed library:

  • FileUploader.Provider
  • FileUploader.Root
  • FileUploader.Dropzone
  • FileUploader.Compact
  • FileUploader.List
  • FileUploader.VirtualList
  • FileUploader.Item (FileUploaderItemView)
  • FileUploader.Actions
  • FileUploader.Rejections

Adapters

The file uploader works with any design system. Each slot takes a component, so a library’s own surface, buttons, avatar and progress bar can replace the fallbacks while the queue, the validation and the previews stay the same. The Adapters page has ready slot maps for shadcn/ui, MUI, Chakra, Ant Design and Radix Themes, as examples of well-known libraries; the same approach works with your own.

Accessibility

  • The drop zone has role="button" and is in the tab order (tabindex="0", or -1 while disabled, with aria-disabled). It is named by the dropzone label, “Add files” by default. A click, Enter or Space opens the file dialog. Dragging a file over it sets data-dragging and switches the title to the dropHere label.
  • The file input is a real <input type="file">, visually hidden but left in the accessibility tree and named by the same label. It is out of the tab order, because the zone and the Browse button open it, but Chrome exposes it as a second “Add files” button to a screen reader’s browse mode; this is a known library issue. Its accept and multiple follow your props.
  • The Browse button is a <button type="button"> inside the zone, so it never submits a form. A button inside a role="button" element is a nested interactive control, which some screen readers announce poorly; this is a known library issue.
  • Progress is a role="progressbar" with aria-valuenow (the percentage, rounded), aria-valuemin="0" and aria-valuemax="100", named “Uploading” and the file name by the progress label. See it in the uploads guide.
  • Each item’s actions are buttons named by the remove, retry and cancel labels. The names do not include the file name, so a list of several files has several “Remove file” buttons; the button sits in the file’s list item.
  • Image previews are <img> elements with alt text from the preview label (“Preview of photo.png”). Other files show a decorative glyph, hidden from assistive technology.
  • Refused files are listed in a role="status" region with aria-live="polite", and stay until the Dismiss button, named by the dismiss label, clears them. The region appears with the first refusal, which some screen readers do not announce; later refusals are added to it and announced without interrupting. This is a known library issue. See the validation guide.
  • Status changes of an item (Uploading, Uploaded, Failed) are shown as text in the item and are not announced on their own.

With focus on the drop zone, or in the compact layout on the Browse button:

KeysAction
EnterSpaceOpen the file dialog.
TabMove to the Browse button, then to each item's Cancel, Retry and Remove buttons, then to Dismiss when files were refused.

Every label can be translated or replaced; see Labels & locales.

Limitations

FAQ

How does the file uploader send files?

Through the upload function you pass: upload(file, { signal, onProgress }) returns a promise with the stored URL. Use fetch, axios or a presigned PUT. Pass signal on so Cancel stops the request, and call onProgress with a percentage. Uploads are queued, three at a time by default.

See it in the docs

Can I use the file uploader in a form without uploading?

Yes. Leave out upload and nothing is ever sent: the items stay ready and onFilesChange hands you the File objects, so the form sends them with the rest of its fields.

See it in the docs

How do I show the files a record already has?

Pass their URLs as defaultValue. Each becomes a finished item, with a preview when the URL ends in an image extension, and can be removed like any other. onUrlsChange gives you the URLs to save.

See it in the docs

In what unit are maxSize and minSize?

Bytes, so 5 MB is 5 * 1024 * 1024. The hint and the rejection messages show the limit formatted with 1024 per step (5 MB, 512 KB). maxFiles counts the items already in the list.

See it in the docs

Does the file uploader work in Arabic and right to left?

Yes. A locale pack translates the parts' text and the rejection messages, two sections called fileUploader and fileValidation, and writes the sizes in that language's number format. The layout uses logical properties, so it mirrors inside a right-to-left ancestor.

See it in the docs