InputsReact
A crop rectangle over an image, moved and resized by pointer, touch or keys, with zoom, rotation and flips.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<ImageCropper.Root>
<ImageCropper.Viewport>
<ImageCropper.Image />
<ImageCropper.Area>
<ImageCropper.Grid />
<ImageCropper.Handle />
</ImageCropper.Area>
</ImageCropper.Viewport>
<ImageCropper.Status />
</ImageCropper.Root>Examples
Avatar
fixedArea with shape="circle" and aspectRatio 1: the round frame stays still while the photo pans and zooms under it.
Controlled
A controlled value shown as numbers with the reason of each change, a select for the aspect ratio and a Reset button.
Form
A 16:9 crop in a Form with a name on the Field: Save shows the JSON the hidden input sends.
API reference
42 props across 7 parts, in React syntax. className, style, render are listed once, under Shared props.
AreaImageCropper.Area
The crop, a slider that moves with the arrow keys and holds the grid and the handles. Rendered once the image and the viewport are measured. Renders a <div>.
| Data attribute | Description |
|---|---|
data-dragging | Present while the crop area or a handle is being dragged. |
data-resizing | Present while a handle is being dragged. |
data-disabled | Present when the image cropper is disabled. |
data-readonly | Present when the image cropper is read-only. |
data-shape | The shape of the crop: rectangle or circle. |
data-fixed | Present when the crop area is fixed and the image moves under it. |
data-focus-visible | Present when the crop area has keyboard focus. |
GridImageCropper.Grid
Guide lines over the crop area, hidden from assistive technology. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
rowsThe number of rows, written to --grid-rows. | number | 3 |
columnsThe number of columns, written to --grid-columns. | number | 3 |
| Data attribute | Description |
|---|---|
data-rows | The number of rows. |
data-columns | The number of columns. |
data-dragging | Present while the crop area or a handle is being dragged. |
data-resizing | Present while a handle is being dragged. |
HandleImageCropper.Handle
A handle that resizes the crop from an edge or a corner. Not rendered when the crop area is fixed. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
positionrequiredThe edge or corner the handle resizes from, physical. | ImageCropperHandlePosition | — |
tabIndexThe edge handle's tab index; 0 puts it in the Tab order. | number | -1 |
| Data attribute | Description |
|---|---|
data-position | The edge or corner the handle resizes from. |
data-dragging | Present while the handle is being dragged. |
data-disabled | Present when the image cropper is disabled. |
data-edge | Present on an edge handle. |
data-corner | Present on a corner handle. |
ImageImageCropper.Image
The image to crop, placed by the zoom, the pan, the rotation and the flip. Renders an <img>.
| Prop | Type | Default |
|---|---|---|
srcThe image: a URL, or a Blob or File shown through an object URL. | string | Blob | — |
onLoadingStatusChangeCallback fired when the loading status changes. | ((status: ImageLoadingStatus) => void) | — |
| Data attribute | Description |
|---|---|
data-loading | Present while the image is loading. |
data-error | Present when the image failed to load. |
data-flip-horizontal | Present when the flip is 'horizontal' or 'both'. |
data-flip-vertical | Present when the flip is 'vertical' or 'both'. |
RootImageCropper.Root
Groups the parts of the image cropper and holds the crop, the zoom, the rotation and the flip. and a hidden <input> beside it. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
valueThe crop, { x, y, width, height } in integer pixels of the oriented, flipped and rotated | ImageCropperCrop | null | — |
defaultValueThe crop when the component is initially rendered, clamped once the image is measured. | ImageCropperCrop | null | null |
onValueChangeEvent handler called when the crop changes. | ((value: ImageCropperCrop | null, eventDetails: ImageCropperRootChangeEventDetails) => void) | — |
onValueCommittedEvent handler called when an interaction ends: a pointer release, the release of the keys | ((value: ImageCropperCrop | null, eventDetails: ImageCropperRootCommitEventDetails) => void) | — |
zoomThe zoom, 1 being the image fitted in the viewport. | number | — |
defaultZoomThe zoom when the component is initially rendered. | number | 1 |
onZoomChangeEvent handler called when the zoom changes. | ((zoom: number, eventDetails: ImageCropperRootZoomChangeEventDetails) => void) | — |
minZoomThe smallest zoom. | number | 1 |
maxZoomThe largest zoom. | number | 5 |
zoomStepThe zoom a key adds or removes; the wheel and a pinch scale by it. | number | 0.1 |
rotationThe rotation in degrees, clockwise. | number | — |
defaultRotationThe rotation when the component is initially rendered. | number | 0 |
onRotationChangeEvent handler called when the rotation changes. | ((rotation: number, eventDetails: ImageCropperRootZoomChangeEventDetails) => void) | — |
flipThe flip, applied before the rotation. | ImageCropperFlip | — |
defaultFlipThe flip when the component is initially rendered. | ImageCropperFlip | 'none' |
onFlipChangeEvent handler called when the flip changes. | ((flip: ImageCropperFlip, eventDetails: ImageCropperRootZoomChangeEventDetails) => void) | — |
aspectRatioThe width / height ratio of the crop: a number locks it, { min, max } limits it. | ImageCropperAspectRatio | — |
shapeThe shape of the crop. | ImageCropperShape | 'rectangle' |
fixedAreaWhether the crop area is fixed in the centre and the image moves under it. | boolean | false |
minWidthThe smallest crop width in image pixels. | number | 1 |
minHeightThe smallest crop height in image pixels. | number | 1 |
maxWidthThe largest crop width in image pixels. | number | Infinity |
maxHeightThe largest crop height in image pixels. | number | Infinity |
restrictToImageWhether the crop must stay inside the rotated image. | boolean | true |
initialCoverageThe share of each side of the image the automatic crop covers, from 0 to 1. | number | 0.8 |
stepThe distance an arrow key moves the crop, in image pixels. | number | 1 |
largeStepThe distance Shift with an arrow key, Page Up and Page Down move the crop, in image pixels. | number | 10 |
wheelZoomWhen the wheel zooms: with Ctrl or Cmd held, always, or never. | ImageCropperWheelZoom | 'ctrl' |
pinchZoomWhether two pointers on the viewport zoom the image. | boolean | true |
disabledWhether the component should ignore user interaction. | boolean | false |
readOnlyWhether the user should be unable to change the crop. | boolean | false |
nameIdentifies the field when a form is submitted. | string | — |
labelsThe texts, in English by default: the names, the instructions, the value texts and the | Partial<ImageCropperLabels> | — |
localeThe locale of the numbers in the value text and the status messages. | Intl.LocalesArgument | — |
actionsRefA ref to imperative actions: setValue, reset, toBlob, toCanvas and getDetails. | React.RefObject<ImageCropperActions | null> | — |
inputRefA ref to access the hidden input element. | React.Ref<HTMLInputElement> | — |
| Data attribute | Description |
|---|---|
data-disabled | Present when the image cropper is disabled. |
data-readonly | Present when the image cropper is read-only. |
data-ready | Present when the image and the viewport are both measured. |
data-dragging | Present while the crop area or a handle is being dragged. |
data-panning | Present while the image is being panned or pinched. |
data-fixed | Present when the crop area is fixed and the image moves under it. |
data-shape | The shape of the crop: rectangle or circle. |
data-loading | Present while the image is loading. |
data-error | Present when the image failed to load. |
data-valid | Present when the crop is valid (when wrapped in Field.Root). |
data-invalid | Present when the crop is invalid (when wrapped in Field.Root). |
data-dirty | Present when the crop has changed from its first value (when wrapped in Field.Root). |
data-touched | Present when focus has left the image cropper (when wrapped in Field.Root). |
data-filled | Present when the image cropper has a crop (when wrapped in Field.Root). |
data-focused | Present when the crop area or a handle is focused (when wrapped in Field.Root). |
StatusImageCropper.Status
A visually hidden live region that announces the crop after a change settles, and the image's loading and error. Renders a <div>.
ViewportImageCropper.Viewport
The box the image is fitted in. Clips the image and holds the crop area. Renders a <div>.
| Data attribute | Description |
|---|---|
data-ready | Present when the image and the viewport are both measured. |
data-dragging | Present while the crop area or a handle is being dragged. |
data-panning | Present while the image is being panned or pinched. |
data-zoomed | Present when the zoom is above 1. |
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
classNameCSS class applied to the element, or a function that
returns a class based on the component's state. | string | ((state: State) => string | undefined) |
styleStyle applied to the element, or a function that
returns a style object based on the component's state. | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) |
renderAllows you to replace the component's HTML element
with a different tag, or compose it with another component. | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State> |
Keyboard and accessibility
Keyboard
| Keys | Does | Part |
|---|---|---|
| ArrowLeftArrowRight | Moves the crop left or right by the step; with Shift by largeStep | Area |
| ArrowUpArrowDown | Moves the crop up or down; the keys stay physical in a right-to-left page | Area |
| HomeEnd | Moves the crop to the left or right edge of the image | Area |
| PageUpPageDown | Moves the crop up or down by largeStep | Area |
| AltArrowRightAltArrowDown | Widens or heightens the crop, anchored at its top-left; ArrowLeft and ArrowUp shrink it | Area |
| - | Zooms in or out by zoomStep about the crop's centre | Area |
| Escape | During a drag, puts the crop back where it was and keeps a Dialog open | Area |
| Escape | Without a drag, is left to what is around the cropper | Area |
| ArrowUpArrowDown | On the top or bottom edge Handle, moves that edge | Handle |
| Tab | Stops on the Area once; the edge Handles are reached with tabIndex 0 | Handle |
Accessibility
| Part | Sets | So that |
|---|---|---|
| Root | role="group" named by the Field's label or labels, and the hidden input of the form | the cropper is one named control in the page and in the form |
| Area | role="slider" with the role description "crop area", one Tab stop, aria-valuenow the crop's x and aria-valuetext the whole rectangle, and instructions | a screen reader hears the size and the position of the crop on every key, and how to move, resize and zoom it |
| Handle | the four edges: named sliders out of the Tab order with the range their edge can reach; the four corners: aria-hidden, pointer only | a touch screen reader can still resize the crop one edge at a time, and the corners are not read four more times |
| Image | the native img with the app's alt, never hidden, and draggable="false" | the picture keeps its text alternative, and a drag never turns into the browser's own image drag |
| Grid | aria-hidden="true" and no pointer events | the thirds are a picture, not something to read or press |
| Status | a visually hidden polite role="status", empty at first | a zoom, turn or flip made with another control is announced once, after 500 ms |
| Viewport | no role; touch-action that lets the page scroll unless the image can pan or pinch | a one-finger swipe on a picture that cannot move still scrolls the page |
Focus order
- The Area is the only Tab stop; the edge Handles take focus from a press or from script, or join the Tab order with
tabIndex0. - A press on the Area or on the image focuses the Area, and a press on an edge Handle focuses that Handle, without scrolling the page.
- Nothing takes focus on mount, on a change of
src, or on a form reset.