Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Components

Free

Image Cropper for Svelte

Pick the part of a photo to keep, lock the aspect or a circle, and get that region back. For Svelte with Ark UI, when the crop is something you will store.

Framework
UI library

Frame a photo with a draggable rectangle or circle. Lock the aspect when the crop should stay fixed.

Use it when you will store that region later, like a profile photo. A click that only magnifies is Image Zoom. In Svelte, Ark UI puts handles on the selection so the keys can nudge it.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Anatomy

Image Cropper anatomy: root, image, selection, handle, grid
ImageCropper
├── ImageCropperViewport
├── ImageCropperImage
├── ImageCropperSelection
├── ImageCropperHandle
└── ImageCropperGrid

Usage

<script lang="ts">
import {
  ImageCropper, ImageCropperImage, ImageCropperSelection,
} from "@/components/ui/image-cropper";
</script>
<ImageCropper class="aspect-video w-full max-w-lg">
  <ImageCropperImage
    alt="Crop me"
    src="https://images.unsplash.com/photo-1662692735672-544412d65934?w=600&auto=format"
  />
  <ImageCropperSelection />
</ImageCropper>

Demos

Aspect Ratio

aspectRatio is width divided by height, so 16 / 9 stays widescreen while the selection is resized. Lock it for a cover, a video poster, or any slot with a fixed frame, and omit it when the person should be free to drag a tall crop or a wide one. A circle is not a ratio. Set cropShape when the mask itself has to be round.

Preview width

Circle Crop

cropShape="circle" rounds the selection, which is what an avatar upload wants, and ImageCropperSelection still draws the grid and the handles. Use a circle only when the destination is round, because a rectangular crop rounded later with CSS will not match the file you store. The aspect can stay free. The shape is what the mask follows.

Preview width

Controlled Zoom

zoom is held in state so the buttons and the wheel share one number. Those buttons step a quarter at a time and stop between zero and three. Take control when the page needs a visible zoom in and zoom out, or when another control must show the scale. Omit zoom and the cropper owns it, starting from defaultZoom.

Preview width

Fixed Crop Area

fixedCropArea pins the selection and asks the person to pan and zoom the photo under it. Use that for an avatar or a cover whose output size is already decided, where the only choice is which part of the picture lands inside. Leave it off when they should draw the rectangle themselves. Min and max size are the softer limit, for a box that can still move.

Preview width

Initial Crop

initialCrop takes x, y, width, and height, so the first rectangle is not the automatic one. Use it to reopen a saved crop or to start on a known region, and read later drags from onCropChange, which reports those same fields. The component never uploads the file. Your own submit handler sends the image plus that rectangle.

Preview width

Min Max Size

minWidth, minHeight, maxWidth, and maxHeight keep the selection from collapsing or swallowing the frame, and this one stays between 80 pixels and a box of 280 by 160. The minimums default to 40 if you set nothing. Use limits when a tiny crop would be useless or a huge one would break the frame you print into. A fixed crop area is stricter, because that box cannot move at all.

Preview width

Zoom Limits

minZoom and maxZoom cap the wheel. Here the photo can shrink to half and grow only to twice, where the defaults are 1 and 5. Drop the minimum when the picture is larger than the viewport, and keep the maximum tight when a deep zoom would export soft. These props only bound the range, while the controlled demo is about who stores the number.

Preview width

FAQ

How do I lock an Image Cropper's aspect ratio?

Set aspectRatio to width divided by height, such as 16 / 9, as the Aspect Ratio demo does, and omit it when the selection can be resized freely. Set cropShape to circle for a round crop, or leave the default rectangle. The selection uses data-shape so the circle is fully rounded. ImageCropperSelection draws the grid and the handles for you.

How do I control Image Cropper zoom in Svelte?

Keep the zoom number in state when buttons must zoom in and out, and pass it back so the wheel and the buttons agree. Omit zoom and the cropper owns it, with defaultZoom as the start, minZoom defaulting to 1, and maxZoom defaulting to 5, as the Zoom Limits demo tightens. onZoomChange receives details.zoom, and you pass that number as zoom.

How do I stop an Image Cropper selection from growing?

Set minWidth, minHeight, maxWidth, and maxHeight on ImageCropper. The minimums default to 40. Set fixedCropArea when the selection must stay put and people pan and zoom the image instead, as the Fixed Crop Area demo does. flip takes horizontal and vertical booleans, and rotation is degrees, if you need those transforms too.

Can I use an Image Cropper from the keyboard?

Yes, arrow keys nudge the Image Cropper selection by nudgeStep, which defaults to 1 pixel, while Shift and Ctrl or Cmd use nudgeStepShift and nudgeStepCtrl. Alt with an arrow resizes from the bottom or right handle, and plus zooms in while minus zooms out. The selection is the focusable part, so do not hide it if keyboard users must crop.

How do I read the crop from an Image Cropper?

Pass initialCrop with x, y, width, and height when the first rectangle should not be the automatic one. onCropChange then receives details.crop with those same fields as the user drags. Image Cropper does not upload or replace the file. Send the original image plus that rectangle from your own submit handler.

How do I restyle an Image Cropper?

Edit the Image Cropper file in your ui folder. The root sets --cropper-accent, --cropper-handler-size, and --cropper-handler-width, and the handles and grid read them. data-dragging and data-panning mark an active gesture, and data-shape is circle or rectangle. The image itself is data-slot image-cropper-image and should keep an alt.

Should I use Image Cropper or Image Zoom?

Use Image Cropper when people must choose a rectangle or a circle and you need that region. Use Image Zoom when a click should only magnify the picture, SVG, or diagram, with nothing to export. Cropper is the editor and Zoom is a viewer, so they solve different tasks, and a page can use both.

How do I install Image Cropper in Svelte?

Run the command in the Installation section above. It adds ImageCropper, ImageCropperImage, and ImageCropperSelection to your ui folder, and the selection includes the grid and handles. Install from that section rather than pasting a partial tree, and put src and alt on ImageCropperImage, as Usage does. The command uses the shadcn-svelte CLI.

API Reference

ImageCropper

<div> data-slot="image-cropper"

PropType
flip?object

The controlled flip state of the image.

Type
{ horizontal: boolean; vertical: boolean }
defaultFlip?object

The initial flip state to apply to the image.

Type
{ horizontal: boolean; vertical: boolean }
Default
{ horizontal: false, vertical: false }
onFlipChange?(details: FlipChangeDetails) → void

Callback fired when the flip state changes.

Type
(details: FlipChangeDetails) → void
Parameters

details – { flip: FlipState }

Returns
void
rotation?number

The controlled rotation of the image in degrees (0 - 360).

defaultRotation?number

The initial rotation to apply to the image in degrees.

Default
0
onRotationChange?(details: RotationChangeDetails) → void

Callback fired when the rotation changes.

Type
(details: RotationChangeDetails) → void
Parameters

details – { rotation: number }

Returns
void
zoom?number

The controlled zoom level of the image.

defaultZoom?number

The initial zoom factor to apply to the image.

Default
1
onZoomChange?(details: ZoomChangeDetails) → void

Callback fired when the zoom level changes.

Type
(details: ZoomChangeDetails) → void
Parameters

details – { zoom: number }

Returns
void
aspectRatio?number

The aspect ratio to maintain for the crop area (width / height). For example, an aspect ratio of 16 / 9 will maintain a width to height ratio of 16:9. If not provided, the crop area can be freely resized.

cropShape?"rectangle" | "circle"

The shape of the crop area.

Type
"rectangle" | "circle"
Default
"rectangle"
dir?"ltr" | "rtl"

The document's text/writing direction.

Type
"ltr" | "rtl"
Default
"ltr"
fixedCropArea?boolean

Whether the crop area is fixed in size and position.

Default
false
id?string

The unique identifier of the machine.

ids?object

The ids of the image cropper elements.

Type
{ root?: string; viewport?: string; image?: string; selection?: string; handle?: (position: string) → string }
initialCrop?object

The initial rectangle of the crop area. If not provided, a smart default will be computed based on viewport size and aspect ratio.

Type
{ y: number; x: number }
maxHeight?number

The maximum height of the crop area.

Default
Infinity
maxWidth?number

The maximum width of the crop area.

Default
Infinity
maxZoom?number

The maximum zoom factor allowed.

Default
5
minHeight?number

The minimum height of the crop area.

Default
40
minWidth?number

The minimum width of the crop area.

Default
40
minZoom?number

The minimum zoom factor allowed.

Default
1
nudgeStep?number

The base nudge step for keyboard arrow keys (in pixels).

Default
1
nudgeStepCtrl?number

The nudge step when Ctrl/Cmd key is held (in pixels).

Default
50
nudgeStepShift?number

The nudge step when Shift key is held (in pixels).

Default
10
onCropChange?(details: CropChangeDetails) → void

Callback fired when the crop area changes.

Type
(details: CropChangeDetails) → void
Parameters

details – { crop: Rect }

Returns
void
translations?object

Specifies the localized strings that identify accessibility elements and their states.

Type
{ rootLabel?: string; rootRoleDescription?: string; previewLoading?: string; previewDescription?: (details: PreviewDescriptionDetails) → string; selectionLabel?: (details: SelectionLabelDetails) → string; selectionRoleDescription?: string; selectionInstructions?: string; selectionValueText?: (details: SelectionValueTextDetails) → string }
zoomSensitivity?number

Controls how responsive pinch-to-zoom is.

Default
2
zoomStep?number

The amount of zoom applied per wheel step.

Default
0.1
asChild?Snippet<[PropsFn]>

Render your own element: a snippet that gets the part's props to spread on it.

Type
Snippet<[PropsFn]>
class?string

Merged with the part's classes.

AttributeType
data-dragging?string

Present when in the dragging state.

data-fixed?string

Present when fixedCropArea is true.

data-panning?string

Present when panning is true.

data-pinch?string

Present when pinch.

data-shapestring

Mirrors cropShape.

CSS variableType
--crop-heightstring

The height of the Root.

--crop-widthstring

The width of the Root.

--crop-xstring

The crop x value for the Root.

--crop-ystring

The crop y value for the Root.

--image-offset-xstring

The offset position for image.

--image-offset-ystring

The offset position for image.

--image-rotationstring

The image rotation value for the Root.

--image-zoomstring

The image zoom value for the Root.

ImageCropperViewport

<div> data-slot="image-cropper-viewport"

PropType
asChild?Snippet<[PropsFn]>

Render your own element: a snippet that gets the part's props to spread on it.

Type
Snippet<[PropsFn]>
AttributeType
data-disabled?string

Present when disabled.

data-ownedbystring

Id of the root part.

ImageCropperImage

<img> data-slot="image-cropper-image"

PropType
asChild?Snippet<[PropsFn]>

Render your own element: a snippet that gets the part's props to spread on it.

Type
Snippet<[PropsFn]>
class?string

Merged with the part's classes.

AttributeType
data-flip-horizontal?string

Present when flipHorizontal is true.

data-flip-vertical?string

Present when flipVertical is true.

data-ownedbystring

Id of the viewport part.

data-ready?string

Present when isImageReady is true.

ImageCropperSelection

<div> data-slot="image-cropper-selection"

PropType
axis?"horizontal" | "vertical" | "both"

The axis of the grid to show.

Type
"horizontal" | "vertical" | "both"
Default
"both"
asChild?Snippet<[PropsFn]>

Render your own element: a snippet that gets the part's props to spread on it.

Type
Snippet<[PropsFn]>
class?string

Merged with the part's classes.

AttributeType
data-disabled?string

Present when disabled.

data-dragging?string

Present when in the dragging state.

data-measured?string

Present when isMeasured is true.

data-panning?string

Present when panning is true.

data-shapestring

Mirrors cropShape.

CSS variableType
--cropper-accentstring

ImageCropperHandle

<div> data-slot="image-cropper-handle"

PropType
position"n" | "e" | "s" | "w" | "ne" | "se" | "sw" | "nw"

The position of the handle.

Type
"n" | "e" | "s" | "w" | "ne" | "se" | "sw" | "nw"
asChild?Snippet<[PropsFn]>

Render your own element: a snippet that gets the part's props to spread on it.

Type
Snippet<[PropsFn]>
class?string

Merged with the part's classes.

AttributeType
data-disabled?string

Present when disabled.

data-placement?string

The placement of the handle.

data-positionstring

Mirrors handlePosition.

CSS variableType
--cropper-accentstring
--cropper-handler-sizestring
--cropper-handler-widthstring

ImageCropperGrid

<div> data-slot="image-cropper-grid"

PropType
axis"horizontal" | "vertical"

The axis of the grid lines to display.

Type
"horizontal" | "vertical"
asChild?Snippet<[PropsFn]>

Render your own element: a snippet that gets the part's props to spread on it.

Type
Snippet<[PropsFn]>
class?string

Merged with the part's classes.

AttributeType
data-axisstring

The axis to resize.

data-dragging?string

Present when in the dragging state.

data-panning?string

Present when panning is true.

useImageCropper

PropType
flip?object

The controlled flip state of the image.

Type
{ horizontal: boolean; vertical: boolean }
defaultFlip?object

The initial flip state to apply to the image.

Type
{ horizontal: boolean; vertical: boolean }
Default
{ horizontal: false, vertical: false }
onFlipChange?(details: FlipChangeDetails) → void

Callback fired when the flip state changes.

Type
(details: FlipChangeDetails) → void
Parameters

details – { flip: FlipState }

Returns
void
rotation?number

The controlled rotation of the image in degrees (0 - 360).

defaultRotation?number

The initial rotation to apply to the image in degrees.

Default
0
onRotationChange?(details: RotationChangeDetails) → void

Callback fired when the rotation changes.

Type
(details: RotationChangeDetails) → void
Parameters

details – { rotation: number }

Returns
void
zoom?number

The controlled zoom level of the image.

defaultZoom?number

The initial zoom factor to apply to the image.

Default
1
onZoomChange?(details: ZoomChangeDetails) → void

Callback fired when the zoom level changes.

Type
(details: ZoomChangeDetails) → void
Parameters

details – { zoom: number }

Returns
void
aspectRatio?number

The aspect ratio to maintain for the crop area (width / height). For example, an aspect ratio of 16 / 9 will maintain a width to height ratio of 16:9. If not provided, the crop area can be freely resized.

cropShape?"rectangle" | "circle"

The shape of the crop area.

Type
"rectangle" | "circle"
Default
"rectangle"
fixedCropArea?boolean

Whether the crop area is fixed in size and position.

Default
false
id?string

The unique identifier of the machine.

ids?object

The ids of the image cropper elements.

Type
{ root?: string; viewport?: string; image?: string; selection?: string; handle?: (position: string) → string }
initialCrop?object

The initial rectangle of the crop area. If not provided, a smart default will be computed based on viewport size and aspect ratio.

Type
{ y: number; x: number }
maxHeight?number

The maximum height of the crop area.

Default
Infinity
maxWidth?number

The maximum width of the crop area.

Default
Infinity
maxZoom?number

The maximum zoom factor allowed.

Default
5
minHeight?number

The minimum height of the crop area.

Default
40
minWidth?number

The minimum width of the crop area.

Default
40
minZoom?number

The minimum zoom factor allowed.

Default
1
nudgeStep?number

The base nudge step for keyboard arrow keys (in pixels).

Default
1
nudgeStepCtrl?number

The nudge step when Ctrl/Cmd key is held (in pixels).

Default
50
nudgeStepShift?number

The nudge step when Shift key is held (in pixels).

Default
10
onCropChange?(details: CropChangeDetails) → void

Callback fired when the crop area changes.

Type
(details: CropChangeDetails) → void
Parameters

details – { crop: Rect }

Returns
void
translations?object

Specifies the localized strings that identify accessibility elements and their states.

Type
{ rootLabel?: string; rootRoleDescription?: string; previewLoading?: string; previewDescription?: (details: PreviewDescriptionDetails) → string; selectionLabel?: (details: SelectionLabelDetails) → string; selectionRoleDescription?: string; selectionInstructions?: string; selectionValueText?: (details: SelectionValueTextDetails) → string }
zoomSensitivity?number

Controls how responsive pinch-to-zoom is.

Default
2
zoomStep?number

The amount of zoom applied per wheel step.

Default
0.1

With other components