Early bird: 30% off Dashral ProSee pricing

Dashral

Image CropperNew

Move, zoom and turn a picture under a crop of any shape, then draw the crop into a file with cropImage.

$ pnpm dlx shadcn@latest add @dashral/image-cropper
Drag the picture, or use the arrow keys, to move it. Press + or − to zoom.

Installation

$ pnpm dlx shadcn@latest add @dashral/image-cropper

Adds the component, the components it builds on, its npm packages and the theme tokens it uses. Or by its full URL: https://dashral.com/r/image-cropper.json

When to use

  • A picture to frame before it's saved: a cover or banner, a product photo, a crop of a set shape.

When not to use

  • A profile photo or logo in a form: Avatar Upload, which brings the picker and the dialog.
  • A picture only to look at, bigger: Lightbox.

Usage

onCropChange keeps the area; cropImage draws it into a file at the width you ask for.

example.tsx
"use client"

import { useState } from "react"
import { Button } from "@/components/dashral/button"
import { ImageCropper, cropImage, type CropArea } from "@/components/dashral/image-cropper"

export default function CoverImage() {
  const [area, setArea] = useState<CropArea | null>(null)
  const picture = "https://picsum.photos/id/1018/1600/1000"

  async function save() {
    if (!area) return
    const blob = await cropImage(picture, area, { width: 1500, crossOrigin: "anonymous" })
    console.log("upload", new File([blob], "cover.jpg", { type: blob.type }))
  }

  return (
    <div className="grid max-w-xl gap-4">
      <ImageCropper src={picture} aspect={3} crossOrigin="anonymous" onCropChange={setArea} aria-label="Cover image" />
      <Button className="justify-self-end" onClick={save}>
        Save cover
      </Button>
    </div>
  )
}

Using the npm package? Import from @dashral/ui/components/image-cropper instead, here and in the examples; the rest is the same.

Examples

Cover image

aspect={3} for a wide crop, saved as WebP. Drag, pinch, the wheel or the arrow keys move and zoom it.

Drag the picture, or use the arrow keys, to move it. Press + or − to zoom.

API reference

ImageCropper

PropTypeDefault
srcstring

The picture: a URL, or URL.createObjectURL(file). Required.

—
aspectnumber

Width ÷ height of the crop: 1 for a square or circle, 3 for a wide cover.

1
shape"rect" | "circle"

The crop's outline. A circle still crops a square.

"rect"
zoom / defaultZoomnumber

1 fills the crop exactly.

1
onZoomChange(zoom: number) => void—
minZoom / maxZoomnumber1 / 3
rotation / defaultRotationnumber

Degrees, clockwise, a multiple of 90.

0
onRotationChange(rotation: number) => void—
onCropChange(area: CropArea) => void

On every change, and once the picture has loaded.

—
showControlsboolean

The zoom slider and the rotate button under the picture.

true
crossOrigin"anonymous" | "use-credentials"

For a picture on another site that allows it, so cropImage can read it.

—
heightnumber

Of the picture area, in pixels; it's as wide as its container.

288
disabledbooleanfalse
aria-labelstring

The picture area's name.

"Crop area"
classNames{ viewport, image, frame, controls }

Classes for each part.

—
classNamestring

Extra classes, merged with cn() so yours win.

—

cropImage()

cropImage(src, area, { width?, type?, quality?, crossOrigin? }) → Promise<Blob>: draws the crop, turned, at that width (the crop's own size by default). type is "image/jpeg" by default, with a white ground under transparency.

CropArea

In the picture's own pixels, before it's turned.

= { x: number; y: number; width: number; height: number; rotation: number }

Keyboard

KeysAction
TabFocus the picture, then the zoom buttons, slider and rotate
← → ↑ ↓Move the picture (Shift for bigger steps)
+or−Zoom in or out

Accessibility

  • The picture area is a named group that can be focused, and says how to move and zoom it with the keyboard.
  • Every control under it has a name: Zoom out, Zoom, Zoom in, Rotate 90°.
  • A picture that won't load says so in an alert in its place, and the controls stay off.

Motion

  • The picture fades in once it's loaded and placed. Zoom and turns glide (--duration-standard); dragging and pinching follow the finger exactly. Thirds show over the crop while it moves.
  • With reduced motion, zoom and turns jump.

Related blocks

Whole sections built with Image Cropper or made for the same job, ready to drop in. Free blocks come with their code; Pro blocks come with Dashral Pro.

  • Workspace brandingPro

    The workspace's name, logo, cover and colour, previewed live in the sidebar, the sign-in page, emails and invoices.

Browse all 65 blocks