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.