All components

File Upload

Dropzone, file list, and per-file item. Storage-agnostic; app owns the upload pipeline.

react-dropzone docs

Single image

Constrain the dropzone to images and a single file. The library handles the UI; the app owns the upload pipeline (here it's a 2-second mock). Thumbnail previews come from the `preview` prop on FileItem.

    <FileDropzone
      onFilesSelected={start}
      accept={{ "image/*": [".png", ".jpg", ".jpeg", ".webp"] }}
      multiple={false}
    />
    <FileList>
      {rows.map((r) => (
        <FileItem
          key={r.id}
          name={r.file.name}
          size={r.file.size}
          type={r.file.type}
          preview={r.preview}
          status={r.status}
          progress={r.progress}
          onRemove={() => remove(r.id)}
        />
      ))}
    </FileList>

    Multiple PDFs

    Accept only PDFs with a file cap. Non-image files fall back to a document icon thumbnail.

      <FileDropzone
        onFilesSelected={start}
        accept={{ "application/pdf": [".pdf"] }}
        maxFiles={5}
      />

      Any file type

      No `accept` constraint means the dropzone allows any file. Images get thumbnails, everything else gets a type-appropriate icon.

        <FileDropzone onFilesSelected={start} />

        Button with thumbnail

        Compact pattern for forms. `FileButton` opens the picker without the drop area. Compose with `FileThumbnail` for an inline preview.

        <FileThumbnail preview={preview} type={selected?.type} />
        <FileButton
          variant="outline"
          onFilesSelected={(files) => setSelected(files[0])}
          accept={{ "image/*": [".png", ".jpg", ".jpeg", ".webp"] }}
        >
          {selected ? "Change image" : "Upload image"}
        </FileButton>

        Avatar upload (circle)

        Compact circular dropzone for profile photos. Shows the selected image, or an icon fallback. Click or drag to upload. Remove button appears when an image is set.

        <AvatarUpload
          src={src}
          onFilesSelected={(files) => setSrc(URL.createObjectURL(files[0]))}
          onRemove={() => setSrc(undefined)}
        />

        Avatar upload (square)

        Same component with `shape="square"` for rounded-square tiles (app icons, thumbnail previews).

        <AvatarUpload
          src={src}
          shape="square"
          onFilesSelected={(files) => setSrc(URL.createObjectURL(files[0]))}
          onRemove={() => setSrc(undefined)}
        />