Forms
ImageCropper
A dialog for cropping a photo to a shape before it is used: drag the photo (or move it the way the arrow keys point) to choose what shows in the frame, zoom with the slider (or Plus and Minus), and Save hands the cropped image to onCrop as a File, ready for a form or an upload. The frame is a rectangle of aspect, or a circle for a profile photo. It always opens as a centered dialog, so dragging the photo never drags the dialog away.
Install
Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):
npx shadcn@latest init --base base --preset nova --yes --no-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):
npx shadcn@latest add ourostack/ouro-ui/image-cropper#v1.2.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/image-cropperIts tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):
npx shadcn@latest add ourostack/ouro-ui/image-cropper#v1.2.13 ourostack/ouro-ui/image-cropper-tests#v1.2.13import { ImageCropper } from "@/components/ouro/image-cropper/image-cropper";Paths use shadcn's default @/ alias; the CLI rewrites them to yours.
Example
<ImageCropper open={file !== null} onOpenChange={(open) => !open && setFile(null)} image={file} shape="circle" onCrop={upload} />Use instead when
- Use
ImageFieldwithcropinstead to pick a photo and crop it in one field, andFileFieldfor files that need no cropping.
Props
Type: ImageCropperProps.
aspect- Type:
number - Default:
1 - The crop's width divided by its height:
1square,16 / 9wide. image(required)- Type:
File | Blob | string | null - The image to crop: a
Filefrom a picker, aBlob, or a URL the page may read pixels from (same origin or CORS). labels- Type:
ImageCropperLabels - The words it shows and says.
onCrop(required)- Type:
(file: File) => void - Called with the cropped image, as a
File, when the person saves. The dialog then closes. onError- Type:
((reason: ImageCropperFailure) => void) - Called when the image cannot be opened or the crop cannot be saved. The cropper shows and announces
labels.openErrororlabels.saveErroritself; close it or offer another image. onOpenChange(required)- Type:
(open: boolean) => void - Called when it opens or closes: after Save, Cancel, Escape or the close button.
open(required)- Type:
boolean - Whether the cropper's dialog is open.
outputWidth- Type:
number - Default:
1024 - The saved image's width in pixels; its height follows
aspect. shape- Type:
"circle" | "rect" - Default:
"rect" circleshows a round frame for a profile photo; the saved image is still the square around it.title- Type:
string - Default:
"Crop photo" - The dialog's title.
type- Type:
"image/jpeg" | "image/png" | "image/webp" - Default:
"image/jpeg" - The saved image's format.
Don't use
These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.
aspectRatio: Not a prop. The shape isaspect, width over height:aspect={16 / 9}.onChange: Not a prop. The cropped image comes toonCrop:<ImageCropper onCrop={(file) => …} />.round: Not a prop. Ouro UI usesshape:<ImageCropper shape="circle" />.src: Not a prop. The image isimage:<ImageCropper image={file} />.
Types
ImageCropperFailure
What went wrong: open, the browser cannot show the image (a format it cannot decode, or an address it may not read); save, it cannot encode the crop.
type ImageCropperFailure = "open" | "save";ImageCropperLabels
The words the cropper shows and says, for apps in other languages.
interface ImageCropperLabels {
/** @default "Save" */
save?: string | undefined;
/** @default "Cancel" */
cancel?: string | undefined;
/** The zoom slider's label. @default "Zoom" */
zoom?: string | undefined;
/** The crop area's name for screen readers. @default "Photo to crop" */
area?: string | undefined;
/** How to move the photo, read with the crop area. @default "Drag the photo, or move it with the arrow keys, to choose what shows. Plus and minus zoom." */
instructions?: string | undefined;
/** Shown and announced when the browser cannot open the image. @default "This photo could not be opened. Choose another one." */
openError?: string | undefined;
/** Shown and announced when the browser cannot save the crop; Save can be tried again. @default "The photo could not be saved. Try again." */
saveError?: string | undefined;
}