Crops an image to a ratio with drag, pinch, wheel and keyboard, plus a toolbar: ratio presets, zoom, rotate ±90°, flip and reset. It outputs a non-destructive crop rect in source pixels for asset.crops; getCroppedBlob() bakes a copy when you need a new file.
Drag or use the arrow keys to move the image. Scroll, pinch or use the slider to zoom.
Move or zoom the image to see the crop rect
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { ImageCropper } from "@vamos-ui/react/media";
<ImageCropper src={asset} defaultAspect={1.91}
onChange={(rect, preset) => adapter.update(asset.id, { crops: { ...asset.crops, [preset?.name ?? "custom"]: rect } })} />Keyboard: focus the crop area and use the arrow keys to move; the zoom slider takes arrows, Page Up/Down, Home/End.
Free-aspect drag handles are not supported (presets cover most needs).
CropRect coordinates are in the rotated and flipped source space, matching getCroppedBlob.
Examples
Round avatar (HR profile)
shape="round" locks 1:1; transform={false} hides rotate and flip.
Drag or use the arrow keys to move the image. Scroll, pinch or use the slider to zoom.
Named presets
Each preset can carry a name; onChange reports it so you store asset.crops[name].
Drag or use the arrow keys to move the image. Scroll, pinch or use the slider to zoom.
Last saved: —
Save as copy
getCroppedBlob() renders the crop on a canvas (rotation- and flip-aware, optional maxWidth) for adapter.upload.
Drag or use the arrow keys to move the image. Scroll, pinch or use the slider to zoom.
API
ImageCropper
| Prop | Type | Default |
|---|---|---|
srcImage to crop. Must allow CORS for getCroppedBlob. | string | MediaAsset | — |
value / defaultValueInitial crop (e.g. asset.crops.og) in source pixels. | CropRect | null | — |
onChangeFires when a drag, zoom or key press ends. rect = { x, y, width, height, rotation?, flip? }. | (rect: CropRect, preset?: CropPreset) => void | — |
aspect / defaultAspect / onAspectChangeControlled or uncontrolled ratio. | number | "original" | first preset |
presetsRatio buttons; labels get the ratio in the locale's decimals. | { label; value: number | "original"; name? }[] | Original, Square, Social 1.91:1, Landscape 16:9, Classic 4:3, Portrait 4:5 |
shaperound = avatar mask, 1:1. | "rect" | "round" | "rect" |
gridRule-of-thirds grid. | boolean | true |
minZoom / maxZoomZoom range. | number | 1 / 4 |
toolbarShow the toolbar. | boolean | true |
transformAllow rotate and flip. | boolean | true |
heightStage height. | number | string | "22rem" |
labelscropper, zoom, rotateLeft, flipH, reset, preset names… (dictionary namespace imageCropper). | Partial<ImageCropperLabels> | — |
getCroppedBlob(src, rect, options?)
| Prop | Type | Default |
|---|---|---|
typeOutput format. | "image/webp" | "image/jpeg" | "image/png" | "image/webp" |
quality0..1. | number | 0.9 |
maxWidthDownscale wider crops. | number | — |
formatAspectRatio(value, locale?)
| Prop | Type | Default |
|---|---|---|
→16/9 → "16:9", 1.91 → "1.91:1" (tr: "1,91:1"). | string | — |