@ashutoshmishr0/image-cropper
v1.0.0
Published
A beautiful React image cropper modal. Click to pick a file, crop with drag/zoom/resize, get base64 PNG output. Supports circle, square, and rectangle shapes. Works with Next.js App Router.
Maintainers
Readme
@ashutoshmishr0/image-cropper
A beautiful, zero-dependency React image cropper modal. The user clicks a button, picks an image file, and a smooth modal opens with a full crop interface — drag to pan, zoom slider, resize handles, and shape masks. On confirm, the cropped image is returned as a base64 PNG string directly in the component where it is used.
Works with Next.js App Router, Pages Router, and any React application.
Installation
npm install @ashutoshmishr0/image-cropperHow it works
- Wrap any button or element with
<ImageCropper>as children - User clicks the element — file picker opens
- User selects an image — crop modal opens automatically
- User drags, zooms, and adjusts the crop area
- User clicks "Crop & Apply" —
onCropis called with the base64 PNG - Modal closes — use the base64 string however you want (preview, upload, etc.)
Basic Usage
"use client";
import { useState } from "react";
import ImageCropper from "@ashutoshmishr0/image-cropper";
export default function Page() {
const [preview, setPreview] = useState<string | null>(null);
return (
<div>
<ImageCropper onCrop={(base64) => setPreview(base64)} />
{preview && <img src={preview} alt="Cropped" />}
</div>
);
}Custom Trigger Button
Pass any element as children. Clicking it will open the file picker.
<ImageCropper onCrop={(base64) => setPreview(base64)}>
<button className="your-own-button">Upload Photo</button>
</ImageCropper>Examples
Avatar crop (circle, fixed 200x200 output)
<ImageCropper
shape="circle"
outputWidth={200}
outputHeight={200}
onCrop={(base64) => setAvatar(base64)}
>
<img
src={avatar || "/default-avatar.png"}
style={{ width: 80, height: 80, borderRadius: "50%", cursor: "pointer" }}
alt="Click to change avatar"
/>
</ImageCropper>Banner crop (16:9, fixed 1200x675 output)
<ImageCropper
shape="rect"
aspectRatio={16 / 9}
outputWidth={1200}
outputHeight={675}
onCrop={(base64) => setBanner(base64)}
>
<button>Upload Banner</button>
</ImageCropper>Square crop
<ImageCropper
shape="square"
outputWidth={500}
outputHeight={500}
onCrop={(base64) => setImage(base64)}
>
<button>Upload Photo</button>
</ImageCropper>Free crop — no fixed ratio or size
When outputWidth, outputHeight, and aspectRatio are all omitted, the user can resize freely in any direction.
<ImageCropper
onCrop={(base64, cropArea) => {
console.log(base64);
console.log(cropArea); // { x, y, width, height } in natural image pixels
}}
>
<button>Choose Image</button>
</ImageCropper>Open programmatically using ref
"use client";
import { useRef } from "react";
import ImageCropper, { type ImageCropperRef } from "@ashutoshmishr0/image-cropper";
export default function Page() {
const cropperRef = useRef<ImageCropperRef>(null);
return (
<>
<button onClick={() => cropperRef.current?.open()}>
Open Cropper
</button>
<ImageCropper
ref={cropperRef}
onCrop={(base64) => console.log(base64)}
/>
</>
);
}Upload cropped image to server
<ImageCropper
shape="circle"
outputWidth={300}
outputHeight={300}
onCrop={async (base64) => {
const blob = await fetch(base64).then((r) => r.blob());
const formData = new FormData();
formData.append("avatar", blob, "avatar.png");
await fetch("/api/upload", { method: "POST", body: formData });
}}
>
<button>Upload Avatar</button>
</ImageCropper>All Props
| Prop | Type | Default | Description | |---|---|---|---| | onCrop | (base64: string, cropArea: CropArea) => void | required | Called when the user confirms the crop | | children | ReactNode | undefined | Any clickable element that triggers the file picker. If omitted, a default "Choose Image" button is shown. | | shape | "rect" / "circle" / "square" | "rect" | Shape of the crop mask | | outputWidth | number | undefined | Output width in pixels. Locks aspect ratio when combined with outputHeight. | | outputHeight | number | undefined | Output height in pixels | | aspectRatio | number | undefined | Lock crop ratio without fixing output size. Example: 16/9. Ignored when outputWidth and outputHeight are both set. | | cropperWidth | number | 520 | Width of the crop canvas inside the modal | | cropperHeight | number | 420 | Height of the crop canvas inside the modal | | minCropWidth | number | 40 | Minimum crop box width in screen pixels | | minCropHeight | number | 40 | Minimum crop box height in screen pixels | | showGrid | boolean | true | Show rule-of-thirds grid lines inside the crop area | | showZoom | boolean | true | Show the zoom slider | | borderRadius | string | "0px" | Border radius of the crop box. Only applies when shape is "rect". | | confirmLabel | string | "Crop & Apply" | Label for the confirm button | | cancelLabel | string | "Cancel" | Label for the cancel button | | accept | string | "image/*" | Accepted file types for the file picker | | triggerClassName | string | "" | CSS class applied to the trigger wrapper |
Ref Methods
interface ImageCropperRef {
open: () => void; // Programmatically open the file picker
}CropArea Object
The second argument in onCrop contains the crop coordinates in natural image pixels (not screen pixels).
interface CropArea {
x: number; // left position in original image
y: number; // top position in original image
width: number; // width of cropped region
height: number; // height of cropped region
}TypeScript Imports
import ImageCropper from "@ashutoshmishr0/image-cropper";
import type { ImageCropperProps, ImageCropperRef, CropArea, CropShape } from "@ashutoshmishr0/image-cropper";Notes for Next.js
Add "use client" at the top of any file that uses this component.
"use client";
import ImageCropper from "@ashutoshmishr0/image-cropper";The component uses browser APIs — canvas, pointer events, FileReader. It is a client-only component and must not be rendered on the server.
License
MIT
