AvatarEditor
Pick a character, generate a picture, or upload one — behind one dialog.
Click the avatar. The editor opens in place with the character tab first; the upload tab takes a dropped image through a square crop. There is no generate tab here because the demo passes no generateImage.
Three ways in
EditableAvatar is the avatar with a pencil on it, for a profile card. AvatarTrigger is a plain button showing a live CharacterMark of the current choice, for a form row. Both open AvatarEditor and forward the same props; use the editor directly when the host owns the dialog's placement.
The pencil is drawn from the engine's own pencilGlyphPath — the same outline the writing state orbits a character with — rather than from an icon set. There is one pencil in this library, and a second one from elsewhere would sit at a different weight next to a character already carrying the first.
How it closes
onClose reports save, cancel or dismiss (Escape, or a click outside), and the two staged things behave differently on a dismissal:
- A staged character survives it. Shapes and inks are cheap and reversible — every one of them is a valid avatar, and someone who clicked a hexagon and then pressed Escape meant to keep the hexagon. Asking them to find Save for a two-click decision is the wrong ceremony, so
onCommitfires with the staged character. - A staged picture does not. It is a crop nobody has looked at from the outside yet, and replacing a face with a badly framed photo is exactly the change someone would want to think about. Only Save applies it.
Tabs
characterTab={false} hides the character tab — a group avatar has no character to choose. The generate tab appears only when generateImage is supplied; it receives the prompt and an AbortSignal, and should resolve to an image URL or data URL. initialTab picks the opening tab; by default an avatar that already has a picture opens on upload.
Crop helpers
The upload tab is built from exported functions, so a host with its own crop UI can reuse the arithmetic and the export.
Limits are exported too: MAX_UPLOAD_BYTES (25 MB), ZOOM_STEP.
API
| Prop | Type | Default | Description |
|---|---|---|---|
value* | AvatarDraft | — | The committed avatar. Edits start from here. |
characterTab | boolean | true | Hide the Character tab — a group avatar has no character to choose. |
className | string | — | — |
face | "cute" | "eyes" | "line" | — | — |
generateImage | ((prompt: string, signal: AbortSignal) => Promise<string>) | — | Hide the Generate tab unless a host can actually generate an image. |
initialTab | AvatarEditorTab | — | Which tab opens first. Defaults to Upload when there is already a picture. |
lineWeight | number | — | — |
nose | "slant" | "angular" | "hook" | "long" | "curve" | "bow" | null | — | — |
onClose | ((reason: "save" | "cancel" | "dismiss") => void) | — | Called when the editor should go away. `save` and `cancel` are the two buttons; `dismiss` is Escape or a click outside, which keeps the staged character (see the note on `onCommit` below). |
onCommit | ((next: AvatarDraft) => void) | — | Called with the new avatar when the edit is confirmed or silently kept. |
previewPx | number | AVATAR_PREVIEW_PX | Size of the character preview above the picker. Defaults to `AVATAR_PREVIEW_PX`. |
style | CSSProperties | — | — |
AvatarTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
value* | AvatarDraft | — | The current avatar, shown on the button and edited in the dialog. |
characterTab | boolean | — | — |
className | string | — | — |
face | "cute" | "eyes" | "line" | — | — |
generateImage | ((prompt: string, signal: AbortSignal) => Promise<string>) | — | Passed straight to the editor. Without it the Generate tab is hidden. |
lineWeight | number | — | — |
nose | "slant" | "angular" | "hook" | "long" | "curve" | "bow" | null | — | — |
onCommit | ((next: AvatarDraft) => void) | — | — |
sizePx | number | 64 | — |
style | CSSProperties | — | — |
EditableAvatar
| Prop | Type | Default | Description |
|---|---|---|---|
value* | AvatarDraft | — | — |
characterTab | boolean | — | — |
className | string | — | — |
face | "cute" | "eyes" | "line" | — | — |
generateImage | ((prompt: string, signal: AbortSignal) => Promise<string>) | — | — |
lineWeight | number | — | — |
nose | "slant" | "angular" | "hook" | "long" | "curve" | "bow" | null | — | — |
onCommit | ((next: AvatarDraft) => void) | — | — |
sizePx | number | 72 | — |
style | CSSProperties | — | — |
Next
- TemplateDialog — name, instructions, visibility
- Character setup — choosing what an agent looks like