Avatar / profile picture cropping component for React. Resize, crop and rotate your uploaded image using a simple and clean user interface.
- Fully typed, written in TypeScript
- Works with React 17, 18, and 19
- Resize, crop, and rotate
- Rounded or square crop area
- Built-in loading indicator
useAvatarEditorhook for easy access to the editor API- Zero runtime dependencies
npm i react-avatar-editorimport AvatarEditor from 'react-avatar-editor'
function MyEditor() {
return (
<AvatarEditor
image="https://example.com/photo.jpg"
width={250}
height={250}
border={50}
color={[255, 255, 255, 0.6]}
scale={1.2}
rotate={0}
/>
)
}The useAvatarEditor hook provides a clean API to access the editor's methods without managing refs manually. All methods return null if the editor isn't ready or no image is loaded.
import AvatarEditor, { useAvatarEditor } from 'react-avatar-editor'
function MyEditor() {
const editor = useAvatarEditor()
const handleSave = () => {
const canvas = editor.getImageScaledToCanvas()
if (canvas) {
const dataUrl = canvas.toDataURL()
// upload dataUrl to your server
}
}
return (
<div>
<AvatarEditor
ref={editor.ref}
image="https://example.com/photo.jpg"
width={250}
height={250}
border={50}
scale={1.2}
/>
<button onClick={handleSave}>Save</button>
</div>
)
}| Method | Returns | Description |
|---|---|---|
ref |
RefObject |
Pass this to the ref prop of AvatarEditor. |
getImage() |
HTMLCanvasElement | null |
The cropped image at the original resolution. |
getImageScaledToCanvas() |
HTMLCanvasElement | null |
The cropped image scaled to the editor dimensions. |
getCroppingRect() |
object | null |
The crop area as { x, y, width, height } (0–1 range). |
If you prefer using refs directly:
import { useRef } from 'react'
import AvatarEditor, { type AvatarEditorRef } from 'react-avatar-editor'
function MyEditor() {
const editor = useRef<AvatarEditorRef>(null)
return (
<div>
<AvatarEditor
ref={editor}
image="https://example.com/photo.jpg"
width={250}
height={250}
/>
<button
onClick={() => {
const canvas = editor.current?.getImageScaledToCanvas()
}}
>
Save
</button>
</div>
)
}Using react-dropzone:
import AvatarEditor from 'react-avatar-editor'
import Dropzone from 'react-dropzone'
function MyEditor() {
const [image, setImage] = useState('https://example.com/photo.jpg')
return (
<Dropzone onDrop={([file]) => setImage(file)} noClick noKeyboard>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<AvatarEditor width={250} height={250} image={image} />
<input {...getInputProps()} />
</div>
)}
</Dropzone>
)
}The rotate prop can be animated using any animation library. Here's an example with motion:
import { useState } from 'react'
import { useMotionValue, useSpring, useMotionValueEvent } from 'motion/react'
import AvatarEditor from 'react-avatar-editor'
function MyEditor() {
const [rotate, setRotate] = useState(0)
const [animatedRotate, setAnimatedRotate] = useState(0)
const rotateMotion = useMotionValue(0)
const rotateSpring = useSpring(rotateMotion, { stiffness: 200, damping: 25 })
useMotionValueEvent(rotateSpring, 'change', (v) => setAnimatedRotate(v))
if (rotateMotion.get() !== rotate) {
rotateMotion.set(rotate)
}
return (
<>
<AvatarEditor
image="https://example.com/photo.jpg"
rotate={animatedRotate}
/>
<button onClick={() => setRotate((r) => r - 90)}>↺</button>
<button onClick={() => setRotate((r) => r + 90)}>↻</button>
</>
)
}| Prop | Type | Default | Description |
|---|---|---|---|
image |
string | File |
The URL or File object of the image to edit. | |
width |
number |
200 |
Width of the crop area in pixels. |
height |
number |
200 |
Height of the crop area in pixels. |
border |
number | number[] |
25 |
Border size around the crop area. Use an array [horizontal, vertical] for different values. |
borderRadius |
number |
0 |
Border radius of the crop area. Set to width / 2 for a circle. |
color |
number[] |
[0, 0, 0, 0.5] |
RGBA color of the crop mask overlay. |
borderColor |
number[] |
RGBA color of the 1px border around the crop area. No border if omitted. | |
backgroundColor |
string |
Background color for transparent images (CSS color string). | |
scale |
number |
1 |
Zoom level. 1 = fit, > 1 = zoom in, < 1 = zoom out (requires disableBoundaryChecks). |
rotate |
number |
0 |
Rotation in degrees. |
position |
{ x, y } |
Center of the crop area (0–1 range). Set this + onPositionChange for controlled panning. |
|
style |
CSSProperties |
Additional CSS styles for the canvas element. | |
crossOrigin |
string |
crossOrigin attribute for the image. Use "anonymous" for CORS images. |
|
showGrid |
boolean |
false |
Show a rule-of-thirds grid overlay. |
gridColor |
string |
"#666" |
Color of the grid lines. |
disableBoundaryChecks |
boolean |
false |
Allow the image to be moved outside the crop boundary. |
disableHiDPIScaling |
boolean |
false |
Disable devicePixelRatio scaling. Can improve performance on mobile. |
disableCanvasRotation |
boolean |
true |
When false, the canvas resizes to fit the rotated image. |
onLoadStart |
() => void |
Called when image loading begins. | |
onLoadSuccess |
(image) => void |
Called when the image loads successfully. | |
onLoadFailure |
() => void |
Called when the image fails to load. | |
onImageReady |
() => void |
Called when the image is first painted on the canvas. | |
onImageChange |
() => void |
Called on every visual change (drag, scale, rotate, etc.). | |
onMouseUp |
() => void |
Called when the user releases the mouse after dragging. | |
onMouseMove |
(event) => void |
Called on every mouse/touch move while dragging. | |
onPositionChange |
(position) => void |
Called when the crop position changes. Receives { x, y }. |
|
onRequestScaleChange |
(scale) => void |
Called when the user presses +/- keys to zoom. Receives the requested new scale value. | |
keyboardStep |
number |
1 |
Pixels to move per arrow key press. Shift multiplies by 10. |
pnpm install # install dependencies
pnpm build # build the library
pnpm lint # run oxlint
pnpm fmt # format with oxfmt
pnpm demo:dev # run demo at localhost:3000Thanks to all contributors:
dan-lee, mtlewis, jakerichan, hu9o, ggwzrd, nmn, kukagg, benwiley4000, ruipserra, rdw, sktt, RKJuve, tibotiber, aumayr, yamafaktory, codedmart, chengyin, MahdiHadrich, jyash97, fivenp, pvcresin, shakaman, oyeanuj, dev-nima, kpbp, DedaDev, vitbokisch, pekq, MateusZitelli, kimon89, xaviergonz, jbrumwell, luisrudge, bytor99999, Mitelak, sahanDissanayake, sle-c, YacheLee, yogthesharma, taro-shono, tanguyantoine, lulzsun, mloeks, metacortex, exiify, thinhvoxuan, tvankith, yarikgenza, zhipenglin, TheMcMurder, notjosh, xulww, jimniels, jeffkole, deadlyicon, velezjose, lixiaoyan, brigand, florapdx, kuhelbeher, chris-rudmin, bluej100, dehbmarques, kimorq