react-avatar-editor

March 21, 2026 · View on GitHub

npm version Downloads

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
  • useAvatarEditor hook 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

MethodReturnsDescription
refRefObjectPass this to the ref prop of AvatarEditor.
getImage()HTMLCanvasElement | nullThe cropped image at the original resolution.
getImageScaledToCanvas()HTMLCanvasElement | nullThe cropped image scaled to the editor dimensions.
getCroppingRect()object | nullThe 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

PropTypeDefaultDescription
imagestring | FileThe URL or File object of the image to edit.
widthnumber200Width of the crop area in pixels.
heightnumber200Height of the crop area in pixels.
bordernumber | number[]25Border size around the crop area. Use an array [horizontal, vertical] for different values.
borderRadiusnumber0Border radius of the crop area. Set to width / 2 for a circle.
colornumber[][0, 0, 0, 0.5]RGBA color of the crop mask overlay.
borderColornumber[]RGBA color of the 1px border around the crop area. No border if omitted.
backgroundColorstringBackground color for transparent images (CSS color string).
scalenumber1Zoom level. 1 = fit, > 1 = zoom in, < 1 = zoom out (requires disableBoundaryChecks).
rotatenumber0Rotation in degrees.
position{ x, y }Center of the crop area (0–1 range). Set this + onPositionChange for controlled panning.
styleCSSPropertiesAdditional CSS styles for the canvas element.
crossOriginstringcrossOrigin attribute for the image. Use "anonymous" for CORS images.
showGridbooleanfalseShow a rule-of-thirds grid overlay.
gridColorstring"#666"Color of the grid lines.
disableBoundaryChecksbooleanfalseAllow the image to be moved outside the crop boundary.
disableHiDPIScalingbooleanfalseDisable devicePixelRatio scaling. Can improve performance on mobile.
disableCanvasRotationbooleantrueWhen false, the canvas resizes to fit the rotated image.
onLoadStart() => voidCalled when image loading begins.
onLoadSuccess(image) => voidCalled when the image loads successfully.
onLoadFailure() => voidCalled when the image fails to load.
onImageReady() => voidCalled when the image is first painted on the canvas.
onImageChange() => voidCalled on every visual change (drag, scale, rotate, etc.).
onMouseUp() => voidCalled when the user releases the mouse after dragging.
onMouseMove(event) => voidCalled on every mouse/touch move while dragging.
onPositionChange(position) => voidCalled when the crop position changes. Receives { x, y }.
onRequestScaleChange(scale) => voidCalled when the user presses +/- keys to zoom. Receives the requested new scale value.
keyboardStepnumber1Pixels 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