react-avatar-editor
March 21, 2026 · View on GitHub
Avatar / profile picture cropping component for React. Resize, crop and rotate your uploaded image using a simple and clean user interface.
Features
- 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
Install
npm i react-avatar-editor
Usage
Basic
import 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}
/>
)
}
With useAvatarEditor hook
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>
)
}
Hook methods
| 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). |
With ref (alternative)
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>
)
}
With drag and drop
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>
)
}
Animated rotation
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>
</>
)
}
Props
| 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. |
Contributing
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:3000
Kudos
Thanks 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