Slots

FileUploader has 13 slots. Pass a replacement for any of them in components; every slot you leave out keeps its fallback. Element slots receive DOM props, so a library's own element fits as it is; widget slots receive named props and usually need a few lines of adapter.

NameKindProps it receivesDefault fallbackdata-* attributes
Root<element>

DOM props: HTMLAttributes<HTMLDivElement>

Fallback source
const Root = ({ className, ...props }: UploaderRootSlotProps) => (
  <div className={cx("sfu", className)} {...props} />
);
  • data-disabled
  • data-dragging
  • data-empty
  • data-variant
Dropzone<element>

DOM props: HTMLAttributes<HTMLDivElement>

Fallback source
const Dropzone = ({ className, ...props }: DropzoneSlotProps) => (
  <div className={cx("sfu__zone", className)} {...props} />
);
  • data-disabled
  • data-dragging
Input<element>

DOM props: InputHTMLAttributes<HTMLInputElement>

Fallback source
const Input = (props: UploaderInputSlotProps) => <input className="sfu__input" {...props} />;
—
Icon{widget}
  • dragging: boolean

    Whether a file is currently over the zone.

Fallback source
const Icon = ({ dragging }: UploaderIconSlotProps) => (
  <span className="sfu__icon" data-dragging={dragging || undefined}>
    <Glyph size={24}>
      <path d="M12 16V4" />
      <path d="m7 9 5-5 5 5" />
      <path d="M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2" />
    </Glyph>
  </span>
);
  • data-dragging
Empty{widget}
  • title: ReactNode
  • hint: ReactNode
  • dragging: boolean
Fallback source
const Empty = ({ title, hint, dragging }: UploaderEmptySlotProps) => (
  <span className="sfu__copy">
    <span className="sfu__title">{title}</span>
    {!dragging && <span className="sfu__hint">{hint}</span>}
  </span>
);
—
Trigger{widget}
  • onClick: () => void

    Opens the file dialog.

  • disabled: boolean
  • children: ReactNode
Fallback source
const Trigger = ({ onClick, disabled, children }: UploaderTriggerSlotProps) => (
  <button
    type="button"
    className="sfu__browse"
    disabled={disabled}
    onClick={(event) => {
      /** The zone itself opens the dialog; without this it would open twice. */
      event.stopPropagation();
      onClick();
    }}
  >
    {children}
  </button>
);
—
List<element>

DOM props: HTMLAttributes<HTMLUListElement>

  • ref?: Ref<HTMLUListElement>
Fallback source
const List = ({ className, ...props }: UploaderListSlotProps) => (
  <ul className={cx("sfu__list", className)} {...props} />
);
—
Item<element>

DOM props: LiHTMLAttributes<HTMLLIElement>

Fallback source
const Item = ({ className, ...props }: UploaderItemSlotProps) => (
  <li className={cx("sfu__item", className)} {...props} />
);
  • data-image
  • data-status
Thumbnail{widget}
  • item: UploadItem
  • src?: string

    The object URL, or the stored URL for restored items.

  • isImage: boolean

    Whether the item is an image.

  • alt: string

    Accessible name for the picture.

Fallback source
const Thumbnail = ({ src, isImage, alt }: ThumbnailSlotProps) => {
  const [broken, setBroken] = useState(false);
  return (
  <span className="sfu__thumb">
    {isImage && src && !broken ? (
      <img className="sfu__thumb-img" src={src} alt={alt} onError={() => setBroken(true)} />
    ) : (
      <Glyph>
        <path d="M14 3v4a1 1 0 0 0 1 1h4" />
        <path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2Z" />
      </Glyph>
    )}
  </span>
  );
};
—
ItemMeta{widget}
  • item: UploadItem
  • size: string

    The size, already formatted (2.4 MB). Empty for restored items.

  • status: ReactNode

    Status wording from the labels.

Fallback source
const ItemMeta = ({ item, size, status }: ItemMetaSlotProps) => (
  <span className="sfu__meta">
    <span className="sfu__name" title={item.name}>
      {item.name}
    </span>
    <span className="sfu__sub">
      {item.status === "error" ? (
        <span className="sfu__error">{item.error}</span>
      ) : (
        <>
          {size && <span>{size}</span>}
          {size && status ? <span aria-hidden="true">·</span> : null}
          {status}
        </>
      )}
    </span>
  </span>
);
—
Progress{widget}
  • value: number

    0–100.

  • active: boolean

    Whether the upload is in flight.

  • aria-label: string
Fallback source
const Progress = ({ value, active, ...aria }: UploaderProgressSlotProps) => (
  <span
    className="sfu__progress"
    role="progressbar"
    aria-valuenow={Math.round(value)}
    aria-valuemin={0}
    aria-valuemax={100}
    data-active={active || undefined}
    {...aria}
  >
    <span className="sfu__progress-bar" style={{ inlineSize: `${Math.max(0, Math.min(100, value))}%` }} />
  </span>
);
  • data-active
Action{widget}
  • action: "remove" | "retry" | "cancel"
  • onClick: () => void
  • disabled?: boolean
  • aria-label: string
Fallback source
const Action = ({ action, onClick, disabled, ...aria }: UploaderActionSlotProps) => (
  <button
    type="button"
    className="sfu__action"
    data-action={action}
    disabled={disabled}
    onClick={(event) => {
      /** Actions sit inside the clickable zone in tile mode. */
      event.stopPropagation();
      onClick();
    }}
    {...aria}
  >
    <Glyph size={16}>
      {action === "remove" && (
        <>
          <path d="M18 6 6 18" />
          <path d="m6 6 12 12" />
        </>
      )}
      {action === "retry" && (
        <>
          <path d="M21 12a9 9 0 1 1-3-6.7" />
          <path d="M21 4v5h-5" />
        </>
      )}
      {action === "cancel" && (
        <>
          <rect x="6" y="6" width="12" height="12" rx="1" />
        </>
      )}
    </Glyph>
  </button>
);
  • data-action
Rejections{widget}
  • rejections: Rejection[]
  • onDismiss: () => void
  • dismissLabel: string
Fallback source
const Rejections = ({ rejections, onDismiss, dismissLabel }: RejectionsSlotProps) => (
  <div className="sfu__rejections" role="status" aria-live="polite">
    <ul>
      {rejections.map((rejection, index) => (
        <li key={`${rejection.file.name}-${index}`}>{rejection.message}</li>
      ))}
    </ul>
    <button type="button" className="sfu__action" onClick={onDismiss} aria-label={dismissLabel}>
      <Glyph size={16}>
        <path d="M18 6 6 18" />
        <path d="m6 6 12 12" />
      </Glyph>
    </button>
  </div>
);
—

Props

Every prop of FileUploader, grouped as in the library's documentation. Controlled state comes in pairs: pass a value with its change handler, or only the default to leave the state inside the component.

NameTypeDefaultDescription
Selection
acceptstring | string[]—
multipleboolean

false, like the native input

Allow more than one file. Defaults to false, like the native input.

maxFilesnumber—

How many items the list may hold in total.

maxSizenumber—
minSizenumber—
validate(file: File) => string | null | undefined—

Your own rule. Return a message to refuse the file, or nothing to allow it.

disabledboolean—

Nothing can be picked, dropped or removed.

Value
valueUploadItem<TData>[]—

Controlled items.

onValueChange(items: UploadItem<TData>[]) => void—

Called with the new items after every change.

defaultValueInitialValue<TData>—

Where an uncontrolled uploader starts: items, files or URLs.

onFilesChange(files: File[]) => void—

Convenience for picker mode: the picked files, whenever they change.

onUrlsChange(urls: string[]) => void—

Convenience for upload mode: the stored URLs, whenever they change.

namestring—

The form field name. A plain HTML form then submits every item under it: a file picked in the browser as the file, as <input type="file" name> would, and a file already on a server (restored from a URL, or uploaded) as its URL, so the server can tell what to keep.

Uploading
uploadUploadFn<TData>—

Sends one file. Omit it and nothing is ever uploaded — the component becomes a picker and items stay ready.

autoUploadbooleantrue

Start uploading as soon as files are picked. Defaults to true.

concurrencynumber3

How many uploads may run at once. Defaults to 3.

onUploaded(item: UploadItem<TData>) => void—

Called for each file that finished uploading.

onUploadError(error: unknown, item: UploadItem<TData>) => void—

Called when an upload fails.

onReject(rejections: Rejection[]) => void—

Called with the files the constraints turned away.

Rendering
variantFileUploaderVariantdropzone

How the component is laid out. Defaults to dropzone.

componentsPartial<FileUploaderComponents>—

Replace any part; the rest fall back to SlotsmithProvider's components.fileUploader, then to the built-in ones.

labelsPartial<FileUploaderLabels>—

Override any text. Wins over locale.

localeLocaleInput

the provider's

The language: a locale object, or the tag of one given to SlotsmithProvider. Defaults to the provider's.

validationLabelsPartial<ValidationLabels>—

Wording for rejection messages.

slotPropsFileUploaderSlotProps—

Extra DOM props for the element parts.

previewboolean | "auto"—

Object-URL previews for images. "auto" (the default) means images only.

hideListboolean—

Hide the item list, e.g. when you render the files yourself.

childrenReactNode—

Rendered inside the drop zone, under the title.

captureWindowDropsboolean—

Swallow drops that miss the drop zone, so the browser doesn't open the file.

virtualFileUploaderVirtualOptions—

Renders only the items in view, for batches of hundreds: pass the virtualizer hook (useVirtualizer from @tanstack/react-virtual) as use, and the sizes.

Any other prop is passed to the root <div> element.

Labels & locales

Every string FileUploader shows or announces, with its English default. Pass labels to change some of them for one component, or locale to switch every string to another language. The demo below does both.

KeyTypeEnglish defaultDescription
titleReactNode"Drag and drop, or browse"

The drop zone's headline.

hint(limits: { accept?: string; maxSize?: string; maxFiles?: number }) => ReactNodehint({ accept: "image/*,.pdf", maxSize: "5 MB", maxFiles: 3 })

prints "image/*, .pdf · up to 5 MB · max 3 files"

The line under it; the default names the limits.

dropHereReactNode"Drop to add"

While a file is over the zone.

browseReactNode"Browse"

The browse button.

dropzonestring"Add files"

Accessible name of the drop zone.

removestring"Remove file"
retrystring"Retry upload"
cancelstring"Cancel upload"
progress(name: string) => stringprogress("photo.jpg")

prints "Uploading photo.jpg"

Accessible name of an item's progress bar.

readyReactNode"Ready"

Status wording per item state.

uploadingReactNode"Uploading…"
doneReactNode"Uploaded"
failedReactNode"Failed"
preview(name: string) => stringpreview("photo.jpg")

prints "Preview of photo.jpg"

Accessible name for a preview.

dismissstring"Dismiss"

Dismisses the rejection list.

rejectedTitle(count: number) => ReactNoderejectedTitle(3)

prints "3 files not added"

Announced when files are refused.

Validation messages

The messages for files the constraints turn away, a second labels section. Pass validationLabels to change some of them for one component; a locale pack translates them with the rest.

KeyTypeEnglish defaultDescription
wrongType(name: string) => stringwrongType("photo.jpg")

prints "photo.jpg is not an allowed file type"

A file that matches none of the accept tokens. Receives the file name.

tooLarge(name: string, max: string) => stringtooLarge("photo.jpg", "…")

prints "photo.jpg is too large (max …)"

A file over maxSize. Receives the file name and the limit, formatted (5 MB).

tooSmall(name: string, min: string) => stringtooSmall("photo.jpg", "…")

prints "photo.jpg is too small (min …)"

A file under minSize. Receives the file name and the limit, formatted (1 KB).

tooMany(max: number) => stringtooMany(3)

prints "Only 3 files allowed"

A file past maxFiles, counting the items already listed. Receives the limit.

Belege hierher ziehenimage/* · bis 2 MB

The ready-made language packs, right-to-left support and app-wide locales are on the Languages page.

Styling

The class names the fallbacks of FileUploader put on their elements, and the tokens its stylesheet reads. Set a token on the component, or on any element around it, to restyle it; target a class only for what no token covers.

ClassSlot
.sfu
.sfu__actions—
.sfu__error—
.sfu__compact—
.sfu__hint—
.sfu__input
.sfu__icon
.sfu__copy
.sfu__title—
.sfu__browse
.sfu__thumb
.sfu__thumb-img—
.sfu__meta
.sfu__name—
.sfu__sub—
.sfu__progress
.sfu__progress-bar—
.sfu__action
.sfu__rejections
.sfu__zone
.sfu__list
.sfu__item
TokenLayer
--sfu-accentThis component
--sfu-bgThis component
--sfu-borderThis component
--sfu-dangerThis component
--sfu-font-sizeThis component
--sfu-gapThis component
--sfu-hoverThis component
--sfu-mutedThis component
--sfu-radiusThis component
--sfu-surfaceThis component
--sfu-textThis component
--sfu-tile-sizeThis component
--ss-accentShared by every component
--ss-borderShared by every component
--ss-dangerShared by every component
--ss-font-sizeShared by every component
--ss-hoverShared by every component
--ss-mutedShared by every component
--ss-radiusShared by every component
--ss-surfaceShared by every component
--ss-textShared by every component

Every token switches to its dark value under .dark, [data-theme="dark"], on the page or on any element around the component.

Theming covers the tokens and the ready-made themes.

Last updated