# or
yarn add @zag-js/image-cropper @zag-js/solid
```

## Anatomy

To set up the image cropper correctly, you'll need to understand its anatomy and
how we name its parts.

> Each part includes a `data-part` attribute to help identify them in the DOM.



## Usage

First, import the image cropper package into your project:

```jsx
import * as imageCropper from "@zag-js/image-cropper"
```

The package exports two key functions:

- `machine` â The state machine logic for the cropper.
- `connect` â The function that translates the machine's state to JSX attributes
  and event handlers.

Next, import the required hooks and functions for your framework and use the
image cropper machine in your project ð¥

```jsx
import * as imageCropper from "@zag-js/image-cropper"
import { normalizeProps, useMachine } from "@zag-js/solid"
import { For, createMemo, createUniqueId } from "solid-js"

export function ImageCropper() {
  const service = useMachine(imageCropper.machine, {
    id: createUniqueId(),
  })

  const api = createMemo(() => imageCropper.connect(service, normalizeProps))

  return (
    <div {...api().getRootProps()}>
      <div {...api().getViewportProps()}>
        <img
          src="https://picsum.photos/seed/crop/640/400"
          crossOrigin="anonymous"
          {...api().getImageProps()}
        />

        <div {...api().getSelectionProps()}>
          <For each={imageCropper.handles}>
            {(position) => (
              <div {...api().getHandleProps({ position })}>
                <span />
              </div>
            )}
          </For>
        </div>
      </div>
    </div>
  )
}
```

### Setting the initial crop

Pass an `initialCrop` to start from a specific rectangle. The size is
constrained to your min/max and viewport, and the position is clamped within the
viewport.

```jsx {2-6}
const service = useMachine(imageCropper.machine, {
  initialCrop: { x: 40, y: 40, width: 240, height: 240 },
  aspectRatio: 1, // optional, lock to square
})
const api = imageCropper.connect(service, normalizeProps)
```

### Fixed crop area

Lock the crop window and allow only panning/zooming of the image beneath it by
setting `fixedCropArea: true`.

```jsx {2}
const service = useMachine(imageCropper.machine, {
  fixedCropArea: true,
})
```

### Crop shape and aspect ratio

- `cropShape` can be `"rectangle"` or `"circle"`.
- `aspectRatio` can lock the crop to a width/height ratio. When `aspectRatio` is
  not set and `cropShape` is `"rectangle"`, holding Shift while resizing locks
  to the current ratio.

```jsx {2-3}
const service = useMachine(imageCropper.machine, {
  cropShape: "circle",
  aspectRatio: 1, // ignored for circle
})
```

### Controlling zoom, rotation, and flip

You can configure defaults and limits, and also control them programmatically
using the API.

```jsx {2-6}
const service = useMachine(imageCropper.machine, {
  defaultZoom: 1.25,
  minZoom: 1,
  maxZoom: 5,
  defaultRotation: 0,
  defaultFlip: { horizontal: false, vertical: false },
})
const api = imageCropper.connect(service, normalizeProps)

// Programmatic controls
api.setZoom(2) // zoom to 2x
api.setRotation(90) // rotate to 90 degrees
api.flipHorizontally() // toggle horizontal flip
api.setFlip({ vertical: true }) // set vertical flip on
```

### Programmatic resizing

Use `api.resize(handle, delta)` to resize from any handle programmatically.
Positive `delta` grows outward, negative shrinks inward.

```jsx
// Grow the selection by 8px from the right edge
api.resize("right", 8)
// Shrink from top-left corner by 4px in both axes
api.resize("top-left", -4)
```

### Getting the cropped image

Use `api.getCroppedImage` to export the current crop, taking
zoom/rotation/flip/pan into account.

```jsx
// Blob (default)
const blob = await api.getCroppedImage({ type: "image/png", quality: 0.92 })

// Data URL
const dataUrl = await api.getCroppedImage({
  output: "dataUrl",
  type: "image/jpeg",
  quality: 0.85,
})

// Example usage
if (blob) {
  const url = URL.createObjectURL(blob)
  previewImg.src = url
}
```

### Understanding coordinate systems

The image cropper uses two different coordinate systems:

#### 1. Viewport Coordinates (`api.crop`)

These are the coordinates you see in the UI, relative to the visible viewport:

```jsx
console.log(api.crop)
// { x: 50, y: 30, width: 200, height: 150 }
```

**Characteristics:**
- Relative to the viewport dimensions
- Changes as you zoom and pan
- Perfect for UI rendering and controls
- Used by `initialCrop` and `setCrop()` (when implemented)

#### 2. Natural Image Coordinates (`api.getCropData()`)

These are the absolute pixel coordinates in the original image:

```jsx
const cropData = api.getCropData()
console.log(cropData)
// {
//   x: 250,
//   y: 150,
//   width: 1000,
//   height: 750,
//   rotate: 0,
//   flipX: false,
//   flipY: false
// }
```

**Characteristics:**
- Relative to the original image dimensions
- Independent of zoom/pan/viewport size
- Essential for server-side cropping
- Perfect for state persistence and undo/redo

#### When to use each

**Use viewport coordinates (`api.crop`)** when:
- Rendering UI controls (sliders, displays)
- Setting initial crop area
- Building custom crop UI

**Use natural coordinates (`api.getCropData()`)** when:
- Sending crop data to your backend for server-side processing
- Persisting state (localStorage, database)
- Implementing undo/redo functionality
- Exporting crop configuration to external tools

#### Example: Server-side cropping

```jsx
// Frontend: Get natural coordinates
const cropData = api.getCropData()

// Send to backend
await fetch('/api/crop-image', {
  method: 'POST',
  body: JSON.stringify({
    imageId: 'photo-123',
    crop: cropData, // Natural pixel coordinates
  })
})

// Backend: Crop the original image file
// Use cropData.x, cropData.y, cropData.width, cropData.height
// to crop the actual image file at full resolution
```

#### Transformation example

Here's how the coordinates relate with a zoom of 2x:

```jsx
// Original image: 3000 Ã 2000 pixels
// Viewport: 600 Ã 400 pixels
// Zoom: 2x

// Viewport coordinates (what you see)
api.crop
// { x: 100, y: 80, width: 200, height: 150 }

// Natural coordinates (original image)
api.getCropData()
// { x: 500, y: 400, width: 1000, height: 750, ... }
// Scale factor: 3000 / 600 = 5x
// So 100px in viewport = 500px in original image
```

### Touch and wheel gestures

- Use the mouse wheel over the viewport to zoom at the pointer location.
- Pinch with two fingers to zoom and pan; the machine smooths tiny changes and
  tracks the pinch midpoint.
- Drag on the viewport background to pan the image (when not dragging the
  selection).

### Keyboard nudges

Configure keyboard nudge steps for move/resize:

```jsx {2-4}
const service = useMachine(imageCropper.machine, {
  nudgeStep: 1,
  nudgeStepShift: 10,
  nudgeStepCtrl: 50,
})
```

### Accessibility

- The root is a live region with helpful descriptions of crop, zoom, and
  rotation status.
- The selection exposes slider-like semantics to assistive tech and supports
  keyboard movement, resizing (Alt+Arrows), and zooming (+/-).
- Customize accessible labels and descriptions via `translations`:

```jsx {2-7}
const service = useMachine(imageCropper.machine, {
  translations: {
    rootLabel: "Product image cropper",
    selectionInstructions:
      "Use arrow keys to move, Alt+arrows to resize, and +/- to zoom.",
  },
})
```

## Styling guide

Earlier, we mentioned that each image cropper part has a `data-part` attribute
added to them to select and style them in the DOM.

```css
[data-scope="image-cropper"][data-part="root"] {
  /* styles for the root part */
}

[data-scope="image-cropper"][data-part="viewport"] {
  /* styles for the viewport part */
}

[data-scope="image-cropper"][data-part="image"] {
  /* styles for the image part */
}

[data-scope="image-cropper"][data-part="selection"] {
  /* styles for the selection part */
}

[data-scope="image-cropper"][data-part="handle"] {
  /* styles for the handle part */
}
```

### Selection shapes

The selection can be styled based on its shape:

```css
[data-part="selection"][data-shape="circle"] {
  /* styles for circular selection */
}

[data-part="selection"][data-shape="rectangle"] {
  /* styles for rectangular selection */
}
```

### States

Various states can be styled using data attributes:

```css
[data-part="root"][data-dragging] {
  /* styles when dragging the selection */
}

[data-part="root"][data-fixed] {
  /* styles when the crop area is fixed */
}
```

## Keyboard Interactions

**`ArrowUp`**
Description: Moves the crop selection upward by the configured nudge step. Hold Shift for the `nudgeStepShift` value or Ctrl/Cmd for `nudgeStepCtrl`.

**`ArrowDown`**
Description: Moves the crop selection downward by the configured nudge step. Hold Shift for the `nudgeStepShift` value or Ctrl/Cmd for `nudgeStepCtrl`.

**`ArrowLeft`**
Description: Moves the crop selection to the left by the configured nudge step. Hold Shift for the `nudgeStepShift` value or Ctrl/Cmd for `nudgeStepCtrl`.

**`ArrowRight`**
Description: Moves the crop selection to the right by the configured nudge step. Hold Shift for the `nudgeStepShift` value or Ctrl/Cmd for `nudgeStepCtrl`.

**`Alt + ArrowUp`**
Description: Resizes the crop vertically from the bottom handle, reducing the height. Hold Shift or Ctrl/Cmd for the larger nudge steps.

**`Alt + ArrowDown`**
Description: Resizes the crop vertically from the bottom handle, increasing the height. Hold Shift or Ctrl/Cmd for the larger nudge steps.

**`Alt + ArrowLeft`**
Description: Resizes the crop horizontally from the right handle, reducing the width. Hold Shift or Ctrl/Cmd for the larger nudge steps.

**`Alt + ArrowRight`**
Description: Resizes the crop horizontally from the right handle, increasing the width. Hold Shift or Ctrl/Cmd for the larger nudge steps.

**`+`**
Description: Zooms in on the image. The `=` key performs the same action on keyboards where both symbols share a key.

**`-`**
Description: Zooms out of the image. The `_` key performs the same action on keyboards where both symbols share a key.

## Methods and Properties

### Machine Context

The image cropper machine exposes the following context properties:

**`ids`**
Type: `Partial<{ root: string; viewport: string; image: string; selection: string; handle: (position: string) => string; }>`
Description: The ids of the image cropper elements

**`translations`**
Type: `IntlTranslations`
Description: Specifies the localized strings that identify accessibility elements and their states.

**`initialCrop`**
Type: `Rect`
Description: The initial rectangle of the crop area.
If not provided, a smart default will be computed based on viewport size and aspect ratio.

**`minWidth`**
Type: `number`
Description: The minimum width of the crop area

**`minHeight`**
Type: `number`
Description: The minimum height of the crop area

**`maxWidth`**
Type: `number`
Description: The maximum width of the crop area

**`maxHeight`**
Type: `number`
Description: The maximum height of the crop area

**`aspectRatio`**
Type: `number`
Description: 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`**
Type: `"rectangle" | "circle"`
Description: The shape of the crop area.

**`zoom`**
Type: `number`
Description: The controlled zoom level of the image.

**`rotation`**
Type: `number`
Description: The controlled rotation of the image in degrees (0 - 360).

**`flip`**
Type: `FlipState`
Description: The controlled flip state of the image.

**`defaultZoom`**
Type: `number`
Description: The initial zoom factor to apply to the image.

**`defaultRotation`**
Type: `number`
Description: The initial rotation to apply to the image in degrees.

**`defaultFlip`**
Type: `FlipState`
Description: The initial flip state to apply to the image.

**`zoomStep`**
Type: `number`
Description: The amount of zoom applied per wheel step.

**`zoomSensitivity`**
Type: `number`
Description: Controls how responsive pinch-to-zoom is.

**`minZoom`**
Type: `number`
Description: The minimum zoom factor allowed.

**`maxZoom`**
Type: `number`
Description: The maximum zoom factor allowed.

**`nudgeStep`**
Type: `number`
Description: The base nudge step for keyboard arrow keys (in pixels).

**`nudgeStepShift`**
Type: `number`
Description: The nudge step when Shift key is held (in pixels).

**`nudgeStepCtrl`**
Type: `number`
Description: The nudge step when Ctrl/Cmd key is held (in pixels).

**`onZoomChange`**
Type: `(details: ZoomChangeDetails) => void`
Description: Callback fired when the zoom level changes.

**`onRotationChange`**
Type: `(details: RotationChangeDetails) => void`
Description: Callback fired when the rotation changes.

**`onFlipChange`**
Type: `(details: FlipChangeDetails) => void`
Description: Callback fired when the flip state changes.

**`onCropChange`**
Type: `(details: CropChangeDetails) => void`
Description: Callback fired when the crop area changes.

**`fixedCropArea`**
Type: `boolean`
Description: Whether the crop area is fixed in size and position.

**`dir`**
Type: `"ltr" | "rtl"`
Description: The document's text/writing direction.

**`id`**
Type: `string`
Description: The unique identifier of the machine.

**`getRootNode`**
Type: `() => ShadowRoot | Node | Document`
Description: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

### Machine API

The image cropper `api` exposes the following methods:

**`zoom`**
Type: `number`
Description: The current zoom level of the image.

**`rotation`**
Type: `number`
Description: The current rotation of the image in degrees.

**`flip`**
Type: `FlipState`
Description: The current flip state of the image.

**`crop`**
Type: `Rect`
Description: The current crop area rectangle in viewport coordinates.

**`offset`**
Type: `Point`
Description: The current offset (pan position) of the image.

**`naturalSize`**
Type: `Size`
Description: The natural (original) size of the image.

**`viewportRect`**
Type: `BoundingRect`
Description: The viewport rectangle dimensions and position.

**`dragging`**
Type: `boolean`
Description: Whether the crop area is currently being dragged.

**`panning`**
Type: `boolean`
Description: Whether the image is currently being panned.

**`setZoom`**
Type: `(zoom: number) => void`
Description: Function to set the zoom level of the image.

**`zoomBy`**
Type: `(delta: number) => void`
Description: Function to zoom the image by a relative amount.

**`setRotation`**
Type: `(rotation: number) => void`
Description: Function to set the rotation of the image.

**`rotateBy`**
Type: `(degrees: number) => void`
Description: Function to rotate the image by a relative amount in degrees.

**`setFlip`**
Type: `(flip: Partial<FlipState>) => void`
Description: Function to set the flip state of the image.

**`flipHorizontally`**
Type: `(value?: boolean) => void`
Description: Function to flip the image horizontally. Pass a boolean to set explicitly or omit to toggle.

**`flipVertically`**
Type: `(value?: boolean) => void`
Description: Function to flip the image vertically. Pass a boolean to set explicitly or omit to toggle.

**`resize`**
Type: `(handlePosition: HandlePosition, delta: number) => void`
Description: Function to resize the crop area from a handle programmatically.

**`reset`**
Type: `() => void`
Description: Function to reset the cropper to its initial state.

**`getCroppedImage`**
Type: `(options?: GetCroppedImageOptions) => Promise<string | Blob>`
Description: Function to get the cropped image with all transformations applied.
Returns a Promise that resolves to either a Blob or data URL.

**`getCropData`**
Type: `() => CropData`
Description: 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.

### Data Attributes

**`Root`**

**`data-scope`**: image-cropper
**`data-part`**: root
**`data-fixed`**: 
**`data-shape`**: 
**`data-pinch`**: 
**`data-dragging`**: Present when in the dragging state
**`data-panning`**: 

**`Viewport`**

**`data-scope`**: image-cropper
**`data-part`**: viewport
**`data-disabled`**: Present when disabled

**`Image`**

**`data-scope`**: image-cropper
**`data-part`**: image
**`data-ready`**: 
**`data-flip-horizontal`**: 
**`data-flip-vertical`**: 

**`Selection`**

**`data-scope`**: image-cropper
**`data-part`**: selection
**`data-disabled`**: Present when disabled
**`data-shape`**: 
**`data-measured`**: 
**`data-dragging`**: Present when in the dragging state
**`data-panning`**: 

**`Handle`**

**`data-scope`**: image-cropper
**`data-part`**: handle
**`data-position`**: 
**`data-disabled`**: Present when disabled

**`Grid`**

**`data-scope`**: image-cropper
**`data-part`**: grid
**`data-axis`**: The axis to resize
**`data-dragging`**: Present when in the dragging state
**`data-panning`**: 

### CSS Variables

<CssVarTable name="image-cropper" />
A listbox component that displays a list of selectable options, supporting both
single and multiple selection modes.

## Resources


[Latest version: v1.33.1](https://www.npmjs.com/package/@zag-js/listbox)
[Logic Visualizer](https://zag-visualizer.vercel.app/listbox)
[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/listbox)



**Features**

- Supports single, multiple, or no selection
- Can be controlled or uncontrolled
- Fully managed keyboard navigation (arrow keys, home, end, etc.)
- Vertical and horizontal orientation
- Typeahead to allow focusing the matching item
- Supports items, labels, groups of items
- Supports grid and list layouts

## Installation

To use the listbox machine in your project, run the following command in your
command line:

```bash
npm install @zag-js/listbox @zag-js/solid