Avatar variant

A profile picture is the most common single-file upload there is, and a wide dashed rectangle with a thumbnail row underneath is the wrong shape for it. Set variant="avatar" to swap the presentation for a circular well: the attached image becomes the background of the circle, and the discard button moves to its top-right rim.

<DropUpload variant="avatar" allowedFormats={["image/*"]} />

Only the view changes. Validation, the upload queue and adapters, the callbacks, the locales and the theming are the same core the default variant uses, so anything documented elsewhere still applies here.

AvatarUpload

Because that first snippet is what an avatar field always wants, it ships as a named export with those defaults already applied:

import { AvatarUpload } from "../components/DropUpload"

;<AvatarUpload avatarSize={120} onUploadComplete={(file) => save(file)} />

It renders DropUpload — every prop below, and every prop in the rest of these docs, works on it too.

Size

avatarSize is the diameter of the circle in pixels, 160 by default:

<AvatarUpload avatarSize={96} />

The copy inside the circle is capped at 70% of the diameter and clamped to two lines. Under 100px there is no width left for words at all, so the circle keeps the upload icon alone and the label lives on as the button's accessible name — a small avatar loses the sentence, never the meaning. The discard button stops fitting inside the rim around the same point and overhangs it instead, which reads better than covering the face.

Cropping

An avatar is a circle, and the photo the user picks almost never is. Dropping an image opens the cropper in the same spot the circle was standing: drag to reposition, use the slider to zoom, and the two buttons underneath reject it or send it. What the circle frames is exactly what gets uploaded — the adapter receives the crop, never the original file.

It is on by default. Pass avatarCrop={false} to attach the picture straight away and let CSS do the framing instead:

<AvatarUpload avatarCrop={false} />

avatarCropSize is the side in px of the square the cropper exports, 512 by default:

<AvatarUpload avatarCropSize={256} />

The crop happens before the file joins the queue, so nothing downstream knows it took place: validation, maxFiles, the upload adapter and every callback see a single attached file, the cropped one. Non-image files skip the cropper entirely — there is nothing to frame.

Replacing the image

The circle holds exactly one file. maxFiles is pinned to 1 in this variant whatever you pass, and a new image replaces the one already there instead of being turned away — dropping a second photo is how you change your avatar, so making the user empty the circle first would be a step for nothing.

Drop several files at once and the circle keeps the last of them, the same one that would have won had they been dropped one after another. The rest are ignored silently: there is visibly one plate, so a stack landing on it is not a mistake worth interrupting anyone for.

A file that fails validation is the one exception — it never evicts a good avatar. The picture already in the circle stays, and the reason appears in a short-lived notice underneath ("holiday.pdf isn't an allowed file type"), reported as usual through onFilesRejected. Only when the circle is empty does a rejected file take it, so the error is visible somewhere.

To clear the circle without putting anything in its place, hold the button on the rim — it is the same hold-to-delete control the file list uses, radial countdown included, and discardFileAwaitTime={0} still turns it into a plain click. Keyboard users get a single press instead of a hold.

Copy

The circle has its own short strings, since the default instruction line is too long to stay legible in there. They are part of the locale like everything else, and can be replaced through translations:

<AvatarUpload
  translations={{
    avatarInstructionLabel: "Pick a face",
    avatarDropHere: "Right there",
    avatarReplaceLabel: "Use another one",
  }}
/>

avatarInstructionLabel shows while the circle is empty, avatarDropHere while something is being dragged over it, and avatarReplaceLabel over a scrim when a photo is already in place and the pointer is on it. Passing instructionLabel overrides the empty-state one.

The format and size hints are not drawn inside the circle — the long detail line has nowhere to go there — but they still render underneath it.

Uploads

Progress is drawn as a ring around the rim instead of the linear bar the file list uses. Failures show under the circle with the same retry action:

<AvatarUpload
  uploadAdapter={s3Adapter({ getUploadUrl })}
  onUploadComplete={(file) => setAvatarUrl(file.name)}
/>

Preloading the current avatar

Pass the user's existing picture through initialAttachedFileList and it is painted on mount:

<AvatarUpload
  initialAttachedFileList={[
    {
      name: "avatar.png",
      size: 0,
      type: "image/png",
      preview: user.avatarUrl,
    },
  ]}
/>

Like every other validation here, the image limits are enforced in the browser and can be bypassed. Check the file on your server too.