# Image Cropper
URL: https://ark-ui.com/docs/components/image-cropper
Source: https://raw.githubusercontent.com/chakra-ui/ark/refs/heads/main/website/src/content/pages/components/image-cropper.mdx
Crop and transform images with zoom, rotation, and aspect ratio controls.
---
## Anatomy
```tsx
```
## Examples
### Basic
Set up a basic image cropper. Drag the handles to resize the selection, or drag inside to pan the image.
**Example: basic**
#### React
```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import styles from 'styles/image-cropper.module.css'
export const Basic = () => {
return (
{ImageCropper.handles.map((position) => (
))}
)
}
```
#### Solid
```tsx
import { For } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import styles from 'styles/image-cropper.module.css'
export const Basic = () => {
return (
{(position) => (
)}
)
}
```
#### Vue
```vue
```
#### Svelte
```svelte
{#each ImageCropper.handles as position}
{/each}
```
### Aspect Ratio
Lock the crop area to a specific aspect ratio. Use the `aspectRatio` prop—pass a number like `16/9` for widescreen or
`1` for square.
**Example: aspect-ratio**
#### React
```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { RectangleHorizontalIcon, SquareIcon, RectangleVerticalIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
const aspects = [
{ label: '16:9', value: 16 / 9, icon: RectangleHorizontalIcon },
{ label: '1:1', value: 1, icon: SquareIcon },
{ label: '9:16', value: 9 / 16, icon: RectangleVerticalIcon },
]
export const AspectRatio = () => {
const [aspectRatio, setAspectRatio] = useState(16 / 9)
return (
```
### Circle Crop
Use `cropShape="circle"` for profile pictures or avatars. The selection becomes a circle instead of a rectangle.
**Example: circle**
#### React
```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import styles from 'styles/image-cropper.module.css'
export const Circle = () => {
return (
{ImageCropper.handles.map((position) => (
))}
)
}
```
#### Solid
```tsx
import { For } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import styles from 'styles/image-cropper.module.css'
export const Circle = () => {
return (
{(position) => (
)}
)
}
```
#### Vue
```vue
```
#### Svelte
```svelte
{#each ImageCropper.handles as position}
{/each}
```
### Initial Crop
Start with a pre-defined crop area using the `initialCrop` prop. Pass an object with `x`, `y`, `width`, and `height` in
pixels.
**Example: initial-crop**
#### React
```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import styles from 'styles/image-cropper.module.css'
export const InitialCrop = () => {
return (
Starts with a pre-defined crop area
{ImageCropper.handles.map((position) => (
))}
)
}
```
#### Solid
```tsx
import { For } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import styles from 'styles/image-cropper.module.css'
export const InitialCrop = () => {
return (
Starts with a pre-defined crop area
{(position) => (
)}
)
}
```
#### Vue
```vue
Starts with a pre-defined crop area
```
#### Svelte
```svelte
Starts with a pre-defined crop area
{#each ImageCropper.handles as position}
{/each}
```
### Controlled Zoom
Control zoom programmatically with the `zoom` and `onZoomChange` props. Useful when you want external buttons to zoom in
and out.
**Example: controlled-zoom**
#### React
```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
export const ControlledZoom = () => {
const [zoom, setZoom] = useState(1)
return (
)
}
```
#### Solid
```tsx
import { For, createSignal } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import { FlipHorizontalIcon, FlipVerticalIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
export const Flip = () => {
const [flip, setFlip] = createSignal({ horizontal: false, vertical: false })
return (
setFlip(e.flip)}>
{(position) => (
)}
)
}
```
#### Vue
```vue
(flip = e.flip)">
```
#### Svelte
```svelte
(flip = e.flip)}>
{#each ImageCropper.handles as position}
{/each}
```
### Min and Max Size
Constrain the crop area size with `minWidth`, `minHeight`, `maxWidth`, and `maxHeight`. Keeps the selection within
sensible bounds.
**Example: min-max-size**
#### React
```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import styles from 'styles/image-cropper.module.css'
export const MinMaxSize = () => {
return (
Crop area constrained to min 80px and max 200px
{ImageCropper.handles.map((position) => (
))}
)
}
```
#### Solid
```tsx
import { For } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import styles from 'styles/image-cropper.module.css'
export const MinMaxSize = () => {
return (
Crop area constrained to min 80px and max 200px
{(position) => (
)}
)
}
```
#### Vue
```vue
Crop area constrained to min 80px and max 200px
```
#### Svelte
```svelte
Crop area constrained to min 80px and max 200px
{#each ImageCropper.handles as position}
{/each}
```
### Fixed Crop Area
Set `fixedCropArea` to `true` when the crop area should stay fixed while the image moves underneath. Useful for
overlay-style cropping.
**Example: fixed**
#### React
```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import styles from 'styles/image-cropper.module.css'
export const Fixed = () => {
return (
```
### Crop Preview
Use `getCroppedImage()` from the context to get the cropped result. Call it with `{ output: 'dataUrl' }` for a base64
string you can use in an `img` src.
**Example: crop-preview**
#### React
```tsx
import { ImageCropper, useImageCropper } from '@ark-ui/react/image-cropper'
import { CropIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
export const CropPreview = () => {
const imageCropper = useImageCropper()
const [preview, setPreview] = useState(null)
const handleCrop = async () => {
const result = await imageCropper.getCroppedImage({ output: 'dataUrl' })
if (typeof result === 'string') {
setPreview(result)
}
}
return (
{ImageCropper.handles.map((position) => (
))}
Preview
{preview && }
)
}
```
#### Solid
```tsx
import { For, createSignal, Show } from 'solid-js'
import { ImageCropper, useImageCropper } from '@ark-ui/solid/image-cropper'
import { CropIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
export const CropPreview = () => {
const imageCropper = useImageCropper()
const [preview, setPreview] = createSignal(null)
const handleCrop = async () => {
const result = await imageCropper().getCroppedImage({ output: 'dataUrl' })
if (typeof result === 'string') {
setPreview(result)
}
}
return (
{(position) => (
)}
Preview
)
}
```
#### Vue
```vue
Preview
```
#### Svelte
```svelte
{#each ImageCropper.handles as position}
{/each}
Preview
{#if preview}
{/if}
```
### Reset
The context exposes a `reset()` method that restores the image to its initial state. Handy for an "undo" or "start over"
button.
**Example: reset**
#### React
```tsx
import { ImageCropper, useImageCropper } from '@ark-ui/react/image-cropper'
import { FlipHorizontalIcon, RotateCwIcon, RotateCcwIcon, RefreshCwIcon, ZoomInIcon, ZoomOutIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
export const Reset = () => {
const imageCropper = useImageCropper()
return (
{ImageCropper.handles.map((position) => (
))}
)
}
```
#### Solid
```tsx
import { For } from 'solid-js'
import { ImageCropper, useImageCropper } from '@ark-ui/solid/image-cropper'
import { FlipHorizontalIcon, RotateCwIcon, RotateCcwIcon, RefreshCwIcon, ZoomInIcon, ZoomOutIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
export const Reset = () => {
const imageCropper = useImageCropper()
return (
{(position) => (
)}
)
}
```
#### Vue
```vue
```
#### Svelte
```svelte
{#each ImageCropper.handles as position}
{/each}
```
### Events
Listen to `onCropChange` and `onZoomChange` to track crop position and zoom level. Use these to sync with external state
or show live previews.
**Example: events**
#### React
```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { useState } from 'react'
import styles from 'styles/image-cropper.module.css'
export const Events = () => {
const [cropData, setCropData] = useState({ x: 0, y: 0, width: 0, height: 0 })
const [zoom, setZoom] = useState(1)
return (
Position
{Math.round(cropData.x)}, {Math.round(cropData.y)}
Size
{Math.round(cropData.width)} x {Math.round(cropData.height)}
```
### Context
Use `ImageCropper.Context` to access the cropper API from anywhere inside the root. You get methods like `zoomBy`,
`rotateBy`, and `setZoom`.
**Example: context**
#### React
```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
export const Context = () => {
return (
{(context) => (
{context.zoom.toFixed(1)}x
)}
{ImageCropper.handles.map((position) => (
))}
)
}
```
#### Solid
```tsx
import { For } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
export const Context = () => {
return (
{/snippet}
{#each ImageCropper.handles as position}
{/each}
```
### Root Provider
Use `RootProvider` with `useImageCropper` when you need to control the cropper from outside the component tree. Build
custom toolbars or integrate with form state.
**Example: root-provider**
#### React
```tsx
import { ImageCropper, useImageCropper } from '@ark-ui/react/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
export const RootProvider = () => {
const imageCropper = useImageCropper()
return (
{imageCropper.zoom.toFixed(1)}x
{ImageCropper.handles.map((position) => (
))}
)
}
```
#### Solid
```tsx
import { For } from 'solid-js'
import { ImageCropper, useImageCropper } from '@ark-ui/solid/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
export const RootProvider = () => {
const imageCropper = useImageCropper()
return (
{imageCropper().zoom.toFixed(1)}x
{(position) => (
)}
)
}
```
#### Vue
```vue
{{ imageCropper.zoom.toFixed(1) }}x
```
#### Svelte
```svelte
{imageCropper().zoom.toFixed(1)}x
{#each ImageCropper.handles as position}
{/each}
```
## API Reference
### Props
### Context
**API:**
| Property | Type | Description |
|----------|------|-------------|
| `zoom` | `number` | The current zoom level of the image. |
| `rotation` | `number` | The current rotation of the image in degrees. |
| `flip` | `FlipState` | The current flip state of the image. |
| `crop` | `Rect` | The current crop area rectangle in viewport coordinates. |
| `offset` | `Point` | The current offset (pan position) of the image. |
| `naturalSize` | `Size` | The natural (original) size of the image. |
| `viewportRect` | `BoundingRect` | The viewport rectangle dimensions and position. |
| `dragging` | `boolean` | Whether the crop area is currently being dragged. |
| `panning` | `boolean` | Whether the image is currently being panned. |
| `setZoom` | `(zoom: number) => void` | Function to set the zoom level of the image. |
| `zoomBy` | `(delta: number) => void` | Function to zoom the image by a relative amount. |
| `setRotation` | `(rotation: number) => void` | Function to set the rotation of the image. |
| `rotateBy` | `(degrees: number) => void` | Function to rotate the image by a relative amount in degrees. |
| `setFlip` | `(flip: Partial) => void` | Function to set the flip state of the image. |
| `flipHorizontally` | `(value?: boolean) => void` | Function to flip the image horizontally. Pass a boolean to set explicitly or omit to toggle. |
| `flipVertically` | `(value?: boolean) => void` | Function to flip the image vertically. Pass a boolean to set explicitly or omit to toggle. |
| `resize` | `(handlePosition: HandlePosition, delta: number) => void` | Function to resize the crop area from a handle programmatically. |
| `reset` | `() => void` | Function to reset the cropper to its initial state. |
| `getCroppedImage` | `(options?: GetCroppedImageOptions) => Promise` | Function to get the cropped image with all transformations applied.
Returns a Promise that resolves to either a Blob or data URL. |
| `getCropData` | `() => CropData` | Function to get the crop data in natural image pixel coordinates.
These coordinates are relative to the original image dimensions,
accounting for zoom, rotation, and flip transformations.
Use this for server-side cropping or state persistence. |