Open-source project
ValentinH/react-easy-crop avatar
ValentinH/react-easy-crop

react-easy-crop owns the whole parent box and injects its own CSS

A React component to crop images/videos with easy interactions

2,777 stars179 forksTypeScriptMIT

At a glance

What is it?
react-easy-crop is a React component for cropping images and video, released as version 6.2.3 with a React 16.4 peer floor. It positions itself absolutely inside whatever you wrap it in, injects its stylesheet into the document head by default, and carries exactly one documented known issue, about modal animations.
Who is it for?
react-easy-crop fits a product that needs an avatar or thumbnail cropper and does not want to build drag, zoom and rotate handling itself. Six hosted examples cover the common shapes, and the crop result arrives twice, in percentages and in pixels, which is what most callers actually need.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The cropper measures its parent, so a scaling modal animation breaks it

There is one entry under known issues, and it is about layout rather than about cropping. If you use the Cropper inside a modal, you should make sure there is no opening animation that changes the modal's dimensions, such as a scaling effect. Fading or sliding animations are fine. Four issue numbers are attached to that rule, #428, #409, #267 and #400.

The mechanism is in the styling note rather than in the issue. The Cropper is styled with position: absolute so that it takes the full space of its parent, and you are told to wrap it in an element using position: relative or the Cropper will fill the whole page. So its box is not its own: it is the parent's box, read at layout time. A scale animation changes the box the user sees without changing the box the component measured, and the crop area ends up sized for one of those two frames rather than the other.

That gives the component two layout contracts and no fallback for either. There is no sizing prop in the visible part of the props table to override the parent box, and the guidance is to fix the container instead of configuring the cropper.

The manifest declares no side effects while the component injects styles

Styles are handled for you by default. The component injects the styles it requires into the document head, so there is nothing to import and nothing to configure for the common case. If you want to own that, there is a prop for it: set disableAutomaticStylesInjection to true and inject the stylesheet yourself from the file shipped in the package, react-easy-crop/react-easy-crop.css.

The package manifest declares sideEffects as false. That field is the flag a bundler reads when deciding whether importing a module can be dropped without changing behaviour, and an automatic injection into the document head is precisely a side effect: the module changes the document whether or not you use its export.

So the two halves of the packaging pull in opposite directions. The default behaviour is a side effect, the manifest says there are none, and the documented escape hatch is a prop plus a manual CSS import. A consumer whose bundler takes the manifest at its word can end up with a working component and no styles, and the failure would look like a layout bug rather than a packaging one.

The MIT page recommends a commercial editor with a referral parameter

The features section is short: drag, zoom and rotate interactions, crop dimensions delivered as both pixels and percentages, any image format as a url or a base 64 string, any video format HTML5 supports, and mobile support. After it comes the paragraph that has nothing to do with those features. If react-easy-crop does not cover your needs, the page recommends Pintura, described as offering cropping, rotating, flipping, filtering and annotating on mobile and desktop.

The link carries a referral parameter, pqina.nl/pintura/?ref=reacteasycrop, which names this project in the commercial product's url. There is a second learn more link with the same parameter, and the badge row at the top of the page includes a link to Intuit's auto project.

None of that changes the licence, which is MIT in both the manifest and the repository, or the fact that the component does cropping only. It does mean the project's own page is the place where the boundary of the free tool is drawn, and it is drawn by pointing at a paid editor rather than by describing what is missing.

A React 16.4 floor and two releases fifteen minutes apart

Installation is two one-line options:

shell
pnpm add react-easy-crop

or

shell
npm install react-easy-crop --save

The peer requirements are react and react-dom at 16.4.0 or newer, which is a floor set years back and still the declared contract. The manifest also pins the publish registry to registry.npmjs.org, names pnpm 11.5.2 as the package manager, and carries four keywords.

The version story is tidy at the top and crowded underneath. The manifest reads 6.2.3 and the newest release tag is v6.2.3 from 2026-07-24. The two before it are v6.2.2 and v6.2.1, both dated 2026-07-08, and their timestamps are fifteen minutes apart, so patch and minor fixes shipped together in one sitting. The last push on the branch is dated 2026-09-10.

The repository is a pnpm workspace with pnpm-workspace.yaml and a lock file, and the documentation site is a second package inside it: separate scripts start, build and deploy the docs by pointing pnpm at the docs directory.

The build is a bundler, two node scripts and a type test

The build script is a chain of three steps: run tsdown, then build:copy-files, then test:build-types. The configuration for the bundler is tsdown.config.mts. The two node scripts in the scripts directory do the other two jobs, one copying files into the output and one checking the built types, which means the published type declarations are verified after bundling rather than trusted from the source. A prebuild step clears the output directory with rimraf before any of that runs.

The version bump is wired to the same build. postversion runs pnpm build, so publishing a new version number is what triggers producing the artifacts for it.

Alongside that sit the checks a contributor runs: a type check with tsc and no emit, a lint pass over the examples and the source directory, and a format command that writes with prettier across the source and the documentation's JavaScript and CSS. Nothing in the lint or format scripts touches the tests directory or the cypress folder, which is where the end to end code lives.

Three test layers, and the visual one runs against a production mode server

The test script runs three things in sequence: unit, then e2e, then lint. Unit is vitest run src, so it is pointed at the source directory specifically. The repository also has a separate tests directory at the root, which this command does not name, so whatever is in there is reached by another route.

The end to end layer starts a server and waits for it. The local script boots vite against the examples directory on port 3001 and then runs cypress run. The CI variant swaps in a different start command, one that boots the same examples with a production mode flag, and waits on the same port before running the same cypress task. So the difference between running the browser tests on a laptop and in CI is the Vite mode the examples are served in.

The visual layer is Percy. The CI script wraps cypress run with percy exec and a record flag, which is how pixel differences between runs are captured. On top of that there is a cypress open script for driving the runner locally, a cypress config file, and a vitest config file. Type checking is separate again, with tsc and no emit.

Six hosted examples, and the crop result arrives twice

The demo page links six hosted examples, all under the project's github pages site. There is a basic example, one that outputs the cropped image, one where the image is selected by the user, one with a round crop area and no grid, one with a video, and one where a crop is saved and loaded again.

The basic usage snippet is the whole integration surface. You hold the crop position in state as an x and y pair starting at zero, the zoom in state starting at one, and you pass both down along with an aspect ratio and the three change handlers. The completion callback is given two arguments, a cropped area and a cropped area in pixels, which is where the pixels and percentages feature comes from: the caller receives the same crop in both units without converting it.

The props table shows how little is mandatory. The image prop is a string, the video prop is a string or an array of objects with a src and an optional type, and one of the two is required. The crop prop is the only one marked required, and its zero value is defined as centring the media under the cropper.

Two lint configs, a CodeSandbox folder and an automated updater

The root of the repository carries the usual spread of a long lived project: .all-contributorsrc, a CodeSandbox directory, both .eslintrc.json and .eslintignore, a prettier config, a vscode directory, a changelog, a code of conduct, a licence, the documentation directory, the examples directory, the scripts directory, the source, the tests and the TypeScript configuration. The lint script passes an extension filter over the examples and the source directory, which is a configuration style from before flat config eslint.

Two automation hooks are worth noticing. There is an auto.config.js and an auto script, and the badge row links to Intuit's auto project, so dependency updates are handled by a bot rather than by dependabot. And the examples exist twice over: once as a directory built by vite for the hosted demos, and once as a CodeSandbox folder for people who want to try the component without cloning.

The page also credits community tutorials, one on building an image gallery with live cropping and one on cropping an avatar and downloading the result, each linking to the author's channel rather than to a docs page.

Editorial conclusion

react-easy-crop fits a product that needs an avatar or thumbnail cropper and does not want to build drag, zoom and rotate handling itself. Six hosted examples cover the common shapes, and the crop result arrives twice, in percentages and in pixels, which is what most callers actually need. Three things to check in your own app. The wrapper needs position: relative or the cropper fills the page, and any modal you put it in must not scale while opening. The CSS is injected for you unless you turn that off, and the manifest declares the package free of side effects, so if your bundler drops that injection you need the manual stylesheet. And note the page itself recommends a commercial editor with a referral parameter when react-easy-crop does not cover your needs.

Frequently asked questions

What is React Easy crop?

It is a React component to crop images and videos with easy interactions, supporting drag, zoom and rotate, returning the crop in both pixels and percentages, accepting any image format as a url or base 64 string and any HTML5 supported video format. It is MIT licensed and currently at version 6.2.3.

how to use react easy crop

Hold the crop position and zoom in state, then pass them to the Cropper along with an aspect ratio, the media and the change handlers. The onCropComplete callback receives the cropped area and the cropped area in pixels, and the examples directory holds runnable versions for basic, output, upload, round, video and restore cases.

How do I install react-easy-crop?

Run pnpm add react-easy-crop, or npm install react-easy-crop --save. React and react-dom are peer dependencies at 16.4.0 or newer, and the manifest pins the publish registry to registry.npmjs.org.

Why does react-easy-crop fill my whole page?

The Cropper is styled with position: absolute so it takes the full space of its parent, so the element you wrap it in has to use position: relative. Without that wrapper the cropper has no bounded parent and fills the page.

Why is the crop area wrong inside a modal?

A modal with an opening animation that changes its dimensions, such as a scaling effect, breaks the measurement. Fading or sliding animations are fine. Four issue threads cover it, numbered 428, 409, 267 and 400.

Does react-easy-crop need a CSS import?

Not by default, because the component injects the styles it needs into the document head. Set disableAutomaticStylesInjection to true to turn that off and inject react-easy-crop/react-easy-crop.css yourself from the package instead.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. ValentinH/react-easy-crop on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/valentinh-react-easy-crop.svg)](https://hysenlabs.com/projects/valentinh-react-easy-crop)