Command Palette

Search for a command to run...

Image Crop

A responsive, pointer- and keyboard-driven image cropping tool. Draw, drag, and 8-way resize a crop over any image or video, with fixed aspect ratios, circular crops, min/max bounds, rule-of-thirds guides, and percent/pixel crop callbacks. Self-contained Tailwind styling, zero dependencies.

A responsive image cropping tool. Wrap any <img> (or <video>) and the user can draw a new crop, drag it around, and resize it from any of the eight handles. A masked overlay dims everything outside the selection. The crop is a controlled value: set it from onChange and pass it straight back in.

Every change reports the crop twice — once in pixels and once in percent. Keep the percent crop in state so the selection stays correct when the container resizes, and use the pixel crop to drive a canvas preview or an upload. Pointer interaction uses pointer capture, and the selection and handles are keyboard-accessible: focus the crop and nudge it with the arrow keys (Shift for ×10, Ctrl/Cmd for ×100).

It is self-contained: zero dependencies, all styling is plain Tailwind, and the only dynamic values (the selection box and the SVG mask) are applied inline.

Installation

One-time setup: add the @ikui registry to your components.json.

components.json
{
  "registries": {
    "@ikui": "https://ik-ui.pages.dev/r/{name}.json"
  }
}

Then install the component:

pnpm dlx shadcn@latest add @ikui/image-crop

Usage

import { ImageCrop, type Crop } from "@/components/image-crop";
const [crop, setCrop] = useState<Crop>();

<ImageCrop crop={crop} onChange={(_, percentCrop) => setCrop(percentCrop)}>
  <img src="/photo.jpg" alt="" />
</ImageCrop>;

Examples

Fixed aspect ratio with preview

Pass aspect to lock the crop to a ratio — here 16 / 9. The helper functions makeAspectCrop and centerCrop build a centered initial crop on image load, and onComplete feeds the pixel crop into a <canvas> preview.

Circular crop

Set circularCrop to render the selection as a circle — pair it with aspect={1} to keep it round (it warps to an oval otherwise). The mask is the only thing that changes; the crop is still a rectangle, so clip the <canvas> to an ellipse to get an avatar with transparent corners.

Custom styling

The selection and handles ship with a teal default, but selectionClassName and handleClassName are merged onto them (later classes win via cn), so you can restyle the crop region without forking the component — here a dashed white border with small square handles.

Props

PropTypeDefault
onChange
(crop: PixelCrop, percentCrop: PercentCrop) => void
-
crop
Crop
-
aspect
number
-
circularCrop
boolean
false
ruleOfThirds
boolean
false
disabled
boolean
false
locked
boolean
false
keepSelection
boolean
false
minWidth
number
-
minHeight
number
-
maxWidth
number
-
maxHeight
number
-
onComplete
(crop: PixelCrop, percentCrop: PercentCrop) => void
-
onDragStart
(e: PointerEvent) => void
-
onDragEnd
(e: PointerEvent) => void
-
renderSelectionAddon
(state: ImageCropState) => ReactNode
-
selectionClassName
string
-
handleClassName
string
-