# Image Cropper

URL: https://ark-ui.com/docs/components/image-cropper
Source: https://raw.githubusercontent.com/chakra-ui/ark/refs/heads/main/website/src/content/pages/components/image-cropper.mdx

Crop and transform images with zoom, rotation, and aspect ratio controls.

---

<ComponentPreview id="ImageCropper" />

## Anatomy

<Anatomy id="image-cropper" />

```tsx
<ImageCropper.Root>
  <ImageCropper.Viewport>
    <ImageCropper.Image />
    <ImageCropper.Selection>
      <ImageCropper.Handle />
      <ImageCropper.Grid />
    </ImageCropper.Selection>
  </ImageCropper.Viewport>
</ImageCropper.Root>
```

## Examples

### Basic

Set up a basic image cropper. Drag the handles to resize the selection, or drag inside to pan the image.

**Example: basic**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const Basic = () => {
  return (
    <div className={styles.Layout}>
      <ImageCropper.Root className={styles.Root}>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { For } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const Basic = () => {
  return (
    <div class={styles.Layout}>
      <ImageCropper.Root class={styles.Root}>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import styles from 'styles/image-cropper.module.css'
</script>

<template>
  <div :class="styles.Layout">
    <ImageCropper.Root :class="styles.Root">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import styles from 'styles/image-cropper.module.css'
</script>

<div class={styles.Layout}>
  <ImageCropper.Root class={styles.Root}>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Aspect Ratio

Lock the crop area to a specific aspect ratio. Use the `aspectRatio` prop—pass a number like `16/9` for widescreen or
`1` for square.

**Example: aspect-ratio**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { RectangleHorizontalIcon, SquareIcon, RectangleVerticalIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

const aspects = [
  { label: '16:9', value: 16 / 9, icon: RectangleHorizontalIcon },
  { label: '1:1', value: 1, icon: SquareIcon },
  { label: '9:16', value: 9 / 16, icon: RectangleVerticalIcon },
]

export const AspectRatio = () => {
  const [aspectRatio, setAspectRatio] = useState(16 / 9)

  return (
    <div className={styles.Layout}>
      <div className={button.Group}>
        {aspects.map((aspect) => (
          <button
            key={aspect.label}
            className={button.Root}
            data-variant={aspectRatio === aspect.value ? 'solid' : undefined}
            onClick={() => setAspectRatio(aspect.value)}
          >
            <aspect.icon />
            {aspect.label}
          </button>
        ))}
      </div>

      <ImageCropper.Root className={styles.Root} aspectRatio={aspectRatio}>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { For, createSignal } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import { RectangleHorizontalIcon, SquareIcon, RectangleVerticalIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

const aspects = [
  { label: '16:9', value: 16 / 9, icon: RectangleHorizontalIcon },
  { label: '1:1', value: 1, icon: SquareIcon },
  { label: '9:16', value: 9 / 16, icon: RectangleVerticalIcon },
]

export const AspectRatio = () => {
  const [aspectRatio, setAspectRatio] = createSignal(16 / 9)

  return (
    <div class={styles.Layout}>
      <div class={button.Group}>
        <For each={aspects}>
          {(aspect) => (
            <button
              class={button.Root}
              data-variant={aspectRatio() === aspect.value ? 'solid' : undefined}
              onClick={() => setAspectRatio(aspect.value)}
            >
              <aspect.icon />
              {aspect.label}
            </button>
          )}
        </For>
      </div>

      <ImageCropper.Root class={styles.Root} aspectRatio={aspectRatio()}>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import { RectangleHorizontal, Square, RectangleVertical } from 'lucide-vue-next'
import { ref, markRaw } from 'vue'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

const aspects = [
  { label: '16:9', value: 16 / 9, icon: markRaw(RectangleHorizontal) },
  { label: '1:1', value: 1, icon: markRaw(Square) },
  { label: '9:16', value: 9 / 16, icon: markRaw(RectangleVertical) },
]

const aspectRatio = ref(16 / 9)
</script>

<template>
  <div :class="styles.Layout">
    <div :class="button.Group">
      <button
        v-for="aspect in aspects"
        :key="aspect.label"
        :class="button.Root"
        :data-variant="aspectRatio === aspect.value ? 'solid' : undefined"
        @click="aspectRatio = aspect.value"
      >
        <component :is="aspect.icon" />
        {{ aspect.label }}
      </button>
    </div>

    <ImageCropper.Root :class="styles.Root" :aspectRatio="aspectRatio">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import { RectangleHorizontalIcon, SquareIcon, RectangleVerticalIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/image-cropper.module.css'

  const aspects = [
    { label: '16:9', value: 16 / 9, icon: RectangleHorizontalIcon },
    { label: '1:1', value: 1, icon: SquareIcon },
    { label: '9:16', value: 9 / 16, icon: RectangleVerticalIcon },
  ]

  let aspectRatio = $state(16 / 9)
</script>

<div class={styles.Layout}>
  <div class={button.Group}>
    {#each aspects as aspect}
      <button
        type="button"
        class={button.Root}
        data-variant={aspectRatio === aspect.value ? 'solid' : undefined}
        onclick={() => (aspectRatio = aspect.value)}
      >
        <aspect.icon />
        {aspect.label}
      </button>
    {/each}
  </div>

  <ImageCropper.Root class={styles.Root} {aspectRatio}>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Circle Crop

Use `cropShape="circle"` for profile pictures or avatars. The selection becomes a circle instead of a rectangle.

**Example: circle**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const Circle = () => {
  return (
    <div className={styles.Layout}>
      <ImageCropper.Root className={styles.Root} cropShape="circle">
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { For } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const Circle = () => {
  return (
    <div class={styles.Layout}>
      <ImageCropper.Root class={styles.Root} cropShape="circle">
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import styles from 'styles/image-cropper.module.css'
</script>

<template>
  <div :class="styles.Layout">
    <ImageCropper.Root :class="styles.Root" cropShape="circle">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import styles from 'styles/image-cropper.module.css'
</script>

<div class={styles.Layout}>
  <ImageCropper.Root class={styles.Root} cropShape="circle">
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Initial Crop

Start with a pre-defined crop area using the `initialCrop` prop. Pass an object with `x`, `y`, `width`, and `height` in
pixels.

**Example: initial-crop**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const InitialCrop = () => {
  return (
    <div className={styles.Layout}>
      <p className={styles.Description}>Starts with a pre-defined crop area</p>

      <ImageCropper.Root className={styles.Root} initialCrop={{ x: 50, y: 30, width: 200, height: 120 }}>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { For } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const InitialCrop = () => {
  return (
    <div class={styles.Layout}>
      <p class={styles.Description}>Starts with a pre-defined crop area</p>

      <ImageCropper.Root class={styles.Root} initialCrop={{ x: 50, y: 30, width: 200, height: 120 }}>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import styles from 'styles/image-cropper.module.css'
</script>

<template>
  <div :class="styles.Layout">
    <p :class="styles.Description">Starts with a pre-defined crop area</p>

    <ImageCropper.Root :class="styles.Root" :initialCrop="{ x: 50, y: 30, width: 200, height: 120 }">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import styles from 'styles/image-cropper.module.css'
</script>

<div class={styles.Layout}>
  <p class={styles.Description}>Starts with a pre-defined crop area</p>

  <ImageCropper.Root class={styles.Root} initialCrop={{ x: 50, y: 30, width: 200, height: 120 }}>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Controlled Zoom

Control zoom programmatically with the `zoom` and `onZoomChange` props. Useful when you want external buttons to zoom in
and out.

**Example: controlled-zoom**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const ControlledZoom = () => {
  const [zoom, setZoom] = useState(1)

  return (
    <div className={styles.Layout}>
      <div className={button.Group}>
        <button className={button.Root} onClick={() => setZoom(zoom - 0.1)}>
          <ZoomOutIcon />
        </button>
        <span style={{ fontSize: '0.875rem', minWidth: '3rem', textAlign: 'center' }}>{zoom.toFixed(1)}x</span>
        <button className={button.Root} onClick={() => setZoom(zoom + 0.1)}>
          <ZoomInIcon />
        </button>
      </div>

      <ImageCropper.Root className={styles.Root} zoom={zoom} onZoomChange={(e) => setZoom(e.zoom)}>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { For, createSignal } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const ControlledZoom = () => {
  const [zoom, setZoom] = createSignal(1)

  return (
    <div class={styles.Layout}>
      <div class={button.Group}>
        <button class={button.Root} onClick={() => setZoom(zoom() - 0.1)}>
          <ZoomOutIcon />
        </button>
        <span style={{ 'font-size': '0.875rem', 'min-width': '3rem', 'text-align': 'center' }}>
          {zoom().toFixed(1)}x
        </span>
        <button class={button.Root} onClick={() => setZoom(zoom() + 0.1)}>
          <ZoomInIcon />
        </button>
      </div>

      <ImageCropper.Root class={styles.Root} zoom={zoom()} onZoomChange={(e) => setZoom(e.zoom)}>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import { ZoomIn, ZoomOut } from 'lucide-vue-next'
import { ref } from 'vue'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

const zoom = ref(1)
</script>

<template>
  <div :class="styles.Layout">
    <div :class="button.Group">
      <button :class="button.Root" @click="zoom -= 0.1">
        <ZoomOut />
      </button>
      <span :style="{ fontSize: '0.875rem', minWidth: '3rem', textAlign: 'center' }">{{ zoom.toFixed(1) }}x</span>
      <button :class="button.Root" @click="zoom += 0.1">
        <ZoomIn />
      </button>
    </div>

    <ImageCropper.Root :class="styles.Root" v-model:zoom="zoom">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import { ZoomInIcon, ZoomOutIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/image-cropper.module.css'

  let zoom = $state(1)
</script>

<div class={styles.Layout}>
  <div class={button.Group}>
    <button type="button" class={button.Root} onclick={() => (zoom -= 0.1)}>
      <ZoomOutIcon />
    </button>
    <span style="font-size: 0.875rem; min-width: 3rem; text-align: center;">{zoom.toFixed(1)}x</span>
    <button type="button" class={button.Root} onclick={() => (zoom += 0.1)}>
      <ZoomInIcon />
    </button>
  </div>

  <ImageCropper.Root class={styles.Root} bind:zoom>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Zoom Limits

Set `minZoom` and `maxZoom` to constrain how far users can zoom. Prevents over-zooming or zooming out past the image
bounds.

**Example: zoom-limits**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const ZoomLimits = () => {
  const [zoom, setZoom] = useState(1)
  const minZoom = 0.5
  const maxZoom = 2

  return (
    <div className={styles.Layout}>
      <div className={button.Group}>
        <button className={button.Root} onClick={() => setZoom(Math.max(minZoom, zoom - 0.1))}>
          <ZoomOutIcon />
        </button>
        <span style={{ fontSize: '0.875rem', padding: '0 0.5rem', minWidth: '3rem', textAlign: 'center' }}>
          {zoom.toFixed(1)}x
        </span>
        <button className={button.Root} onClick={() => setZoom(Math.min(maxZoom, zoom + 0.1))}>
          <ZoomInIcon />
        </button>
      </div>

      <p className={styles.Description}>
        Zoom constrained between {minZoom}x and {maxZoom}x
      </p>

      <ImageCropper.Root
        className={styles.Root}
        zoom={zoom}
        onZoomChange={(e) => setZoom(e.zoom)}
        minZoom={minZoom}
        maxZoom={maxZoom}
      >
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { For, createSignal } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const ZoomLimits = () => {
  const [zoom, setZoom] = createSignal(1)
  const minZoom = 0.5
  const maxZoom = 2

  return (
    <div class={styles.Layout}>
      <div class={button.Group}>
        <button class={button.Root} onClick={() => setZoom(Math.max(minZoom, zoom() - 0.1))}>
          <ZoomOutIcon />
        </button>
        <span style={{ 'font-size': '0.875rem', padding: '0 0.5rem', 'min-width': '3rem', 'text-align': 'center' }}>
          {zoom().toFixed(1)}x
        </span>
        <button class={button.Root} onClick={() => setZoom(Math.min(maxZoom, zoom() + 0.1))}>
          <ZoomInIcon />
        </button>
      </div>

      <p class={styles.Description}>
        Zoom constrained between {minZoom}x and {maxZoom}x
      </p>

      <ImageCropper.Root
        class={styles.Root}
        zoom={zoom()}
        onZoomChange={(e) => setZoom(e.zoom)}
        minZoom={minZoom}
        maxZoom={maxZoom}
      >
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import { ZoomIn, ZoomOut } from 'lucide-vue-next'
import { ref } from 'vue'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

const zoom = ref(1)
const minZoom = 0.5
const maxZoom = 2
</script>

<template>
  <div :class="styles.Layout">
    <div :class="button.Group">
      <button :class="button.Root" @click="zoom = Math.max(minZoom, zoom - 0.1)">
        <ZoomOut />
      </button>
      <span :style="{ fontSize: '0.875rem', padding: '0 0.5rem', minWidth: '3rem', textAlign: 'center' }">
        {{ zoom.toFixed(1) }}x
      </span>
      <button :class="button.Root" @click="zoom = Math.min(maxZoom, zoom + 0.1)">
        <ZoomIn />
      </button>
    </div>

    <p :class="styles.Description">Zoom constrained between {{ minZoom }}x and {{ maxZoom }}x</p>

    <ImageCropper.Root :class="styles.Root" v-model:zoom="zoom" :minZoom="minZoom" :maxZoom="maxZoom">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import { ZoomInIcon, ZoomOutIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/image-cropper.module.css'

  let zoom = $state(1)
  const minZoom = 0.5
  const maxZoom = 2
</script>

<div class={styles.Layout}>
  <div class={button.Group}>
    <button type="button" class={button.Root} onclick={() => (zoom = Math.max(minZoom, zoom - 0.1))}>
      <ZoomOutIcon />
    </button>
    <span style="font-size: 0.875rem; padding: 0 0.5rem; min-width: 3rem; text-align: center;">
      {zoom.toFixed(1)}x
    </span>
    <button type="button" class={button.Root} onclick={() => (zoom = Math.min(maxZoom, zoom + 0.1))}>
      <ZoomInIcon />
    </button>
  </div>

  <p class={styles.Description}>Zoom constrained between {minZoom}x and {maxZoom}x</p>

  <ImageCropper.Root class={styles.Root} bind:zoom {minZoom} {maxZoom}>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Rotation

Rotate the image with the `rotation` and `onRotationChange` props. Values are in degrees—common increments are 90
or 180.

**Example: rotation**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { RotateCcwIcon, RotateCwIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const Rotation = () => {
  const [rotation, setRotation] = useState(0)

  return (
    <div className={styles.Layout}>
      <div className={button.Group}>
        <button className={button.Root} onClick={() => setRotation(rotation - 90)}>
          <RotateCcwIcon />
        </button>
        <button className={button.Root} onClick={() => setRotation(rotation + 90)}>
          <RotateCwIcon />
        </button>
        <span style={{ fontSize: '0.875rem', padding: '0 0.5rem' }}>{rotation}°</span>
      </div>

      <ImageCropper.Root className={styles.Root} rotation={rotation} onRotationChange={(e) => setRotation(e.rotation)}>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { For, createSignal } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import { RotateCcwIcon, RotateCwIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const Rotation = () => {
  const [rotation, setRotation] = createSignal(0)

  return (
    <div class={styles.Layout}>
      <div class={button.Group}>
        <button class={button.Root} onClick={() => setRotation(rotation() - 90)}>
          <RotateCcwIcon />
        </button>
        <button class={button.Root} onClick={() => setRotation(rotation() + 90)}>
          <RotateCwIcon />
        </button>
        <span style={{ 'font-size': '0.875rem', padding: '0 0.5rem' }}>{rotation()}deg</span>
      </div>

      <ImageCropper.Root class={styles.Root} rotation={rotation()} onRotationChange={(e) => setRotation(e.rotation)}>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import { RotateCcw, RotateCw } from 'lucide-vue-next'
import { ref } from 'vue'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

const rotation = ref(0)
</script>

<template>
  <div :class="styles.Layout">
    <div :class="button.Group">
      <button :class="button.Root" @click="rotation -= 90">
        <RotateCcw />
      </button>
      <button :class="button.Root" @click="rotation += 90">
        <RotateCw />
      </button>
      <span :style="{ fontSize: '0.875rem', padding: '0 0.5rem' }">{{ rotation }}deg</span>
    </div>

    <ImageCropper.Root :class="styles.Root" :rotation="rotation" @rotation-change="(e) => (rotation = e.rotation)">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import { RotateCcwIcon, RotateCwIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/image-cropper.module.css'

  let rotation = $state(0)
</script>

<div class={styles.Layout}>
  <div class={button.Group}>
    <button type="button" class={button.Root} onclick={() => (rotation -= 90)}>
      <RotateCcwIcon />
    </button>
    <button type="button" class={button.Root} onclick={() => (rotation += 90)}>
      <RotateCwIcon />
    </button>
    <span style="font-size: 0.875rem; padding: 0 0.5rem;">{rotation}deg</span>
  </div>

  <ImageCropper.Root class={styles.Root} {rotation} onRotationChange={(e) => (rotation = e.rotation)}>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Flip

Flip the image horizontally or vertically using the `flip` prop. Pass an object with `horizontal` and `vertical`
booleans.

**Example: flip**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { FlipHorizontalIcon, FlipVerticalIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const Flip = () => {
  const [flip, setFlip] = useState({ horizontal: false, vertical: false })

  return (
    <div className={styles.Layout}>
      <div className={button.Group}>
        <button
          className={button.Root}
          data-variant={flip.horizontal ? 'solid' : undefined}
          onClick={() => setFlip({ ...flip, horizontal: !flip.horizontal })}
        >
          <FlipHorizontalIcon />
          Horizontal
        </button>
        <button
          className={button.Root}
          data-variant={flip.vertical ? 'solid' : undefined}
          onClick={() => setFlip({ ...flip, vertical: !flip.vertical })}
        >
          <FlipVerticalIcon />
          Vertical
        </button>
      </div>

      <ImageCropper.Root className={styles.Root} flip={flip} onFlipChange={(e) => setFlip(e.flip)}>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { For, createSignal } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import { FlipHorizontalIcon, FlipVerticalIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const Flip = () => {
  const [flip, setFlip] = createSignal({ horizontal: false, vertical: false })

  return (
    <div class={styles.Layout}>
      <div class={button.Group}>
        <button
          class={button.Root}
          data-variant={flip().horizontal ? 'solid' : undefined}
          onClick={() => setFlip({ ...flip(), horizontal: !flip().horizontal })}
        >
          <FlipHorizontalIcon />
          Horizontal
        </button>
        <button
          class={button.Root}
          data-variant={flip().vertical ? 'solid' : undefined}
          onClick={() => setFlip({ ...flip(), vertical: !flip().vertical })}
        >
          <FlipVerticalIcon />
          Vertical
        </button>
      </div>

      <ImageCropper.Root class={styles.Root} flip={flip()} onFlipChange={(e) => setFlip(e.flip)}>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import { FlipHorizontal, FlipVertical } from 'lucide-vue-next'
import { ref } from 'vue'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

const flip = ref({ horizontal: false, vertical: false })
</script>

<template>
  <div :class="styles.Layout">
    <div :class="button.Group">
      <button
        :class="button.Root"
        :data-variant="flip.horizontal ? 'solid' : undefined"
        @click="flip = { ...flip, horizontal: !flip.horizontal }"
      >
        <FlipHorizontal />
        Horizontal
      </button>
      <button
        :class="button.Root"
        :data-variant="flip.vertical ? 'solid' : undefined"
        @click="flip = { ...flip, vertical: !flip.vertical }"
      >
        <FlipVertical />
        Vertical
      </button>
    </div>

    <ImageCropper.Root :class="styles.Root" :flip="flip" @flip-change="(e) => (flip = e.flip)">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import { FlipHorizontalIcon, FlipVerticalIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/image-cropper.module.css'

  let flip = $state({ horizontal: false, vertical: false })
</script>

<div class={styles.Layout}>
  <div class={button.Group}>
    <button
      type="button"
      class={button.Root}
      data-variant={flip.horizontal ? 'solid' : undefined}
      onclick={() => (flip = { ...flip, horizontal: !flip.horizontal })}
    >
      <FlipHorizontalIcon />
      Horizontal
    </button>
    <button
      type="button"
      class={button.Root}
      data-variant={flip.vertical ? 'solid' : undefined}
      onclick={() => (flip = { ...flip, vertical: !flip.vertical })}
    >
      <FlipVerticalIcon />
      Vertical
    </button>
  </div>

  <ImageCropper.Root class={styles.Root} {flip} onFlipChange={(e) => (flip = e.flip)}>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Min and Max Size

Constrain the crop area size with `minWidth`, `minHeight`, `maxWidth`, and `maxHeight`. Keeps the selection within
sensible bounds.

**Example: min-max-size**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const MinMaxSize = () => {
  return (
    <div className={styles.Layout}>
      <p className={styles.Description}>Crop area constrained to min 80px and max 200px</p>

      <ImageCropper.Root className={styles.Root} minWidth={80} minHeight={80} maxWidth={200} maxHeight={200}>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { For } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const MinMaxSize = () => {
  return (
    <div class={styles.Layout}>
      <p class={styles.Description}>Crop area constrained to min 80px and max 200px</p>

      <ImageCropper.Root class={styles.Root} minWidth={80} minHeight={80} maxWidth={200} maxHeight={200}>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import styles from 'styles/image-cropper.module.css'
</script>

<template>
  <div :class="styles.Layout">
    <p :class="styles.Description">Crop area constrained to min 80px and max 200px</p>

    <ImageCropper.Root :class="styles.Root" :minWidth="80" :minHeight="80" :maxWidth="200" :maxHeight="200">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import styles from 'styles/image-cropper.module.css'
</script>

<div class={styles.Layout}>
  <p class={styles.Description}>Crop area constrained to min 80px and max 200px</p>

  <ImageCropper.Root class={styles.Root} minWidth={80} minHeight={80} maxWidth={200} maxHeight={200}>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Fixed Crop Area

Set `fixedCropArea` to `true` when the crop area should stay fixed while the image moves underneath. Useful for
overlay-style cropping.

**Example: fixed**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const Fixed = () => {
  return (
    <div className={styles.Layout}>
      <ImageCropper.Root className={styles.Root} fixedCropArea>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const Fixed = () => {
  return (
    <div class={styles.Layout}>
      <ImageCropper.Root class={styles.Root} fixedCropArea>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import styles from 'styles/image-cropper.module.css'
</script>

<template>
  <div :class="styles.Layout">
    <ImageCropper.Root :class="styles.Root" fixedCropArea>
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import styles from 'styles/image-cropper.module.css'
</script>

<div class={styles.Layout}>
  <ImageCropper.Root class={styles.Root} fixedCropArea>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Crop Preview

Use `getCroppedImage()` from the context to get the cropped result. Call it with `{ output: 'dataUrl' }` for a base64
string you can use in an `img` src.

**Example: crop-preview**

#### React

```tsx
import { ImageCropper, useImageCropper } from '@ark-ui/react/image-cropper'
import { CropIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const CropPreview = () => {
  const imageCropper = useImageCropper()
  const [preview, setPreview] = useState<string | null>(null)

  const handleCrop = async () => {
    const result = await imageCropper.getCroppedImage({ output: 'dataUrl' })
    if (typeof result === 'string') {
      setPreview(result)
    }
  }

  return (
    <div className={styles.Layout}>
      <div className={button.Group}>
        <button className={button.Root} data-variant="solid" onClick={handleCrop}>
          <CropIcon />
          Crop Image
        </button>
      </div>

      <ImageCropper.RootProvider className={styles.Root} value={imageCropper}>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
            crossOrigin="anonymous"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.RootProvider>

      <div className={styles.Preview}>
        <span className={styles.PreviewLabel}>Preview</span>
        {preview && <img src={preview} alt="Cropped preview" className={styles.PreviewImage} />}
      </div>
    </div>
  )
}

```

#### Solid

```tsx
import { For, createSignal, Show } from 'solid-js'
import { ImageCropper, useImageCropper } from '@ark-ui/solid/image-cropper'
import { CropIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const CropPreview = () => {
  const imageCropper = useImageCropper()
  const [preview, setPreview] = createSignal<string | null>(null)

  const handleCrop = async () => {
    const result = await imageCropper().getCroppedImage({ output: 'dataUrl' })
    if (typeof result === 'string') {
      setPreview(result)
    }
  }

  return (
    <div class={styles.Layout}>
      <div class={button.Group}>
        <button class={button.Root} data-variant="solid" onClick={handleCrop}>
          <CropIcon />
          Crop Image
        </button>
      </div>

      <ImageCropper.RootProvider class={styles.Root} value={imageCropper}>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
            crossOrigin="anonymous"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.RootProvider>

      <div class={styles.Preview}>
        <span class={styles.PreviewLabel}>Preview</span>
        <Show when={preview()}>
          <img src={preview()!} alt="Cropped preview" class={styles.PreviewImage} />
        </Show>
      </div>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper, useImageCropper } from '@ark-ui/vue/image-cropper'
import { Crop } from 'lucide-vue-next'
import { ref } from 'vue'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

const imageCropper = useImageCropper()
const preview = ref<string | null>(null)

const handleCrop = async () => {
  const result = await imageCropper.value.getCroppedImage({ output: 'dataUrl' })
  if (typeof result === 'string') {
    preview.value = result
  }
}
</script>

<template>
  <div :class="styles.Layout">
    <div :class="button.Group">
      <button :class="button.Root" data-variant="solid" @click="handleCrop">
        <Crop />
        Crop Image
      </button>
    </div>

    <ImageCropper.RootProvider :class="styles.Root" :value="imageCropper">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
          crossorigin="anonymous"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.RootProvider>

    <div :class="styles.Preview">
      <span :class="styles.PreviewLabel">Preview</span>
      <img v-if="preview" :src="preview" alt="Cropped preview" :class="styles.PreviewImage" />
    </div>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper, useImageCropper } from '@ark-ui/svelte/image-cropper'
  import { CropIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/image-cropper.module.css'

  const imageCropper = useImageCropper()
  let preview = $state<string | null>(null)

  const handleCrop = async () => {
    const result = await imageCropper().getCroppedImage({ output: 'dataUrl' })
    if (typeof result === 'string') {
      preview = result
    }
  }
</script>

<div class={styles.Layout}>
  <div class={button.Group}>
    <button type="button" class={button.Root} data-variant="solid" onclick={handleCrop}>
      <CropIcon />
      Crop Image
    </button>
  </div>

  <ImageCropper.RootProvider class={styles.Root} value={imageCropper}>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
        crossorigin="anonymous"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.RootProvider>

  <div class={styles.Preview}>
    <span class={styles.PreviewLabel}>Preview</span>
    {#if preview}
      <img src={preview} alt="Cropped preview" class={styles.PreviewImage} />
    {/if}
  </div>
</div>

```

### Reset

The context exposes a `reset()` method that restores the image to its initial state. Handy for an "undo" or "start over"
button.

**Example: reset**

#### React

```tsx
import { ImageCropper, useImageCropper } from '@ark-ui/react/image-cropper'
import { FlipHorizontalIcon, RotateCwIcon, RotateCcwIcon, RefreshCwIcon, ZoomInIcon, ZoomOutIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const Reset = () => {
  const imageCropper = useImageCropper()

  return (
    <div className={styles.Layout}>
      <div className={button.Group}>
        <button className={button.Root} onClick={() => imageCropper.zoomBy(-0.1)}>
          <ZoomOutIcon />
        </button>
        <button className={button.Root} onClick={() => imageCropper.zoomBy(0.1)}>
          <ZoomInIcon />
        </button>
        <button className={button.Root} onClick={() => imageCropper.rotateBy(-90)}>
          <RotateCcwIcon />
        </button>
        <button className={button.Root} onClick={() => imageCropper.rotateBy(90)}>
          <RotateCwIcon />
        </button>
        <button className={button.Root} onClick={() => imageCropper.flipHorizontally()}>
          <FlipHorizontalIcon />
        </button>
        <button className={button.Root} data-variant="surface" onClick={() => imageCropper.reset()}>
          <RefreshCwIcon />
          Reset
        </button>
      </div>

      <ImageCropper.RootProvider className={styles.Root} value={imageCropper}>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.RootProvider>
    </div>
  )
}

```

#### Solid

```tsx
import { For } from 'solid-js'
import { ImageCropper, useImageCropper } from '@ark-ui/solid/image-cropper'
import { FlipHorizontalIcon, RotateCwIcon, RotateCcwIcon, RefreshCwIcon, ZoomInIcon, ZoomOutIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const Reset = () => {
  const imageCropper = useImageCropper()

  return (
    <div class={styles.Layout}>
      <div class={button.Group}>
        <button class={button.Root} onClick={() => imageCropper().zoomBy(-0.1)}>
          <ZoomOutIcon />
        </button>
        <button class={button.Root} onClick={() => imageCropper().zoomBy(0.1)}>
          <ZoomInIcon />
        </button>
        <button class={button.Root} onClick={() => imageCropper().rotateBy(-90)}>
          <RotateCcwIcon />
        </button>
        <button class={button.Root} onClick={() => imageCropper().rotateBy(90)}>
          <RotateCwIcon />
        </button>
        <button class={button.Root} onClick={() => imageCropper().flipHorizontally()}>
          <FlipHorizontalIcon />
        </button>
        <button class={button.Root} data-variant="surface" onClick={() => imageCropper().reset()}>
          <RefreshCwIcon />
          Reset
        </button>
      </div>

      <ImageCropper.RootProvider class={styles.Root} value={imageCropper}>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.RootProvider>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper, useImageCropper } from '@ark-ui/vue/image-cropper'
import { FlipHorizontal, RotateCw, RotateCcw, RefreshCw, ZoomIn, ZoomOut } from 'lucide-vue-next'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

const imageCropper = useImageCropper()
</script>

<template>
  <div :class="styles.Layout">
    <div :class="button.Group">
      <button :class="button.Root" @click="imageCropper.zoomBy(-0.1)">
        <ZoomOut />
      </button>
      <button :class="button.Root" @click="imageCropper.zoomBy(0.1)">
        <ZoomIn />
      </button>
      <button :class="button.Root" @click="imageCropper.rotateBy(-90)">
        <RotateCcw />
      </button>
      <button :class="button.Root" @click="imageCropper.rotateBy(90)">
        <RotateCw />
      </button>
      <button :class="button.Root" @click="imageCropper.flipHorizontally()">
        <FlipHorizontal />
      </button>
      <button :class="button.Root" data-variant="surface" @click="imageCropper.reset()">
        <RefreshCw />
        Reset
      </button>
    </div>

    <ImageCropper.RootProvider :class="styles.Root" :value="imageCropper">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.RootProvider>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper, useImageCropper } from '@ark-ui/svelte/image-cropper'
  import {
    FlipHorizontalIcon,
    RotateCwIcon,
    RotateCcwIcon,
    RefreshCwIcon,
    ZoomInIcon,
    ZoomOutIcon,
  } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/image-cropper.module.css'

  const imageCropper = useImageCropper()
</script>

<div class={styles.Layout}>
  <div class={button.Group}>
    <button type="button" class={button.Root} onclick={() => imageCropper().zoomBy(-0.1)}>
      <ZoomOutIcon />
    </button>
    <button type="button" class={button.Root} onclick={() => imageCropper().zoomBy(0.1)}>
      <ZoomInIcon />
    </button>
    <button type="button" class={button.Root} onclick={() => imageCropper().rotateBy(-90)}>
      <RotateCcwIcon />
    </button>
    <button type="button" class={button.Root} onclick={() => imageCropper().rotateBy(90)}>
      <RotateCwIcon />
    </button>
    <button type="button" class={button.Root} onclick={() => imageCropper().flipHorizontally()}>
      <FlipHorizontalIcon />
    </button>
    <button type="button" class={button.Root} data-variant="surface" onclick={() => imageCropper().reset()}>
      <RefreshCwIcon />
      Reset
    </button>
  </div>

  <ImageCropper.RootProvider class={styles.Root} value={imageCropper}>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.RootProvider>
</div>

```

### Events

Listen to `onCropChange` and `onZoomChange` to track crop position and zoom level. Use these to sync with external state
or show live previews.

**Example: events**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { useState } from 'react'
import styles from 'styles/image-cropper.module.css'

export const Events = () => {
  const [cropData, setCropData] = useState({ x: 0, y: 0, width: 0, height: 0 })
  const [zoom, setZoom] = useState(1)

  return (
    <div className={styles.Layout}>
      <ImageCropper.Root
        className={styles.Root}
        onCropChange={(e) => setCropData(e.crop)}
        onZoomChange={(e) => setZoom(e.zoom)}
      >
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>

      <div className={styles.DataDisplay}>
        <div className={styles.DataItem}>
          <span className={styles.DataLabel}>Zoom</span>
          <span className={styles.DataValue}>{zoom.toFixed(2)}x</span>
        </div>
        <div className={styles.DataItem}>
          <span className={styles.DataLabel}>Position</span>
          <span className={styles.DataValue}>
            {Math.round(cropData.x)}, {Math.round(cropData.y)}
          </span>
        </div>
        <div className={styles.DataItem}>
          <span className={styles.DataLabel}>Size</span>
          <span className={styles.DataValue}>
            {Math.round(cropData.width)} × {Math.round(cropData.height)}
          </span>
        </div>
      </div>
    </div>
  )
}

```

#### Solid

```tsx
import { For, createSignal } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import styles from 'styles/image-cropper.module.css'

export const Events = () => {
  const [cropData, setCropData] = createSignal({ x: 0, y: 0, width: 0, height: 0 })
  const [zoom, setZoom] = createSignal(1)

  return (
    <div class={styles.Layout}>
      <ImageCropper.Root
        class={styles.Root}
        onCropChange={(e) => setCropData(e.crop)}
        onZoomChange={(e) => setZoom(e.zoom)}
      >
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>

      <div class={styles.DataDisplay}>
        <div class={styles.DataItem}>
          <span class={styles.DataLabel}>Zoom</span>
          <span class={styles.DataValue}>{zoom().toFixed(2)}x</span>
        </div>
        <div class={styles.DataItem}>
          <span class={styles.DataLabel}>Position</span>
          <span class={styles.DataValue}>
            {Math.round(cropData().x)}, {Math.round(cropData().y)}
          </span>
        </div>
        <div class={styles.DataItem}>
          <span class={styles.DataLabel}>Size</span>
          <span class={styles.DataValue}>
            {Math.round(cropData().width)} x {Math.round(cropData().height)}
          </span>
        </div>
      </div>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import { ref } from 'vue'
import styles from 'styles/image-cropper.module.css'

const cropData = ref({ x: 0, y: 0, width: 0, height: 0 })
const zoom = ref(1)
</script>

<template>
  <div :class="styles.Layout">
    <ImageCropper.Root
      :class="styles.Root"
      @crop-change="(e) => (cropData = e.crop)"
      @zoom-change="(e) => (zoom = e.zoom)"
    >
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>

    <div :class="styles.DataDisplay">
      <div :class="styles.DataItem">
        <span :class="styles.DataLabel">Zoom</span>
        <span :class="styles.DataValue">{{ zoom.toFixed(2) }}x</span>
      </div>
      <div :class="styles.DataItem">
        <span :class="styles.DataLabel">Position</span>
        <span :class="styles.DataValue">{{ Math.round(cropData.x) }}, {{ Math.round(cropData.y) }}</span>
      </div>
      <div :class="styles.DataItem">
        <span :class="styles.DataLabel">Size</span>
        <span :class="styles.DataValue">{{ Math.round(cropData.width) }} x {{ Math.round(cropData.height) }}</span>
      </div>
    </div>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper } from '@ark-ui/svelte/image-cropper'
  import styles from 'styles/image-cropper.module.css'

  let cropData = $state({ x: 0, y: 0, width: 0, height: 0 })
  let zoom = $state(1)
</script>

<div class={styles.Layout}>
  <ImageCropper.Root
    class={styles.Root}
    onCropChange={(e) => (cropData = e.crop)}
    onZoomChange={(e) => (zoom = e.zoom)}
  >
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>

  <div class={styles.DataDisplay}>
    <div class={styles.DataItem}>
      <span class={styles.DataLabel}>Zoom</span>
      <span class={styles.DataValue}>{zoom.toFixed(2)}x</span>
    </div>
    <div class={styles.DataItem}>
      <span class={styles.DataLabel}>Position</span>
      <span class={styles.DataValue}>
        {Math.round(cropData.x)}, {Math.round(cropData.y)}
      </span>
    </div>
    <div class={styles.DataItem}>
      <span class={styles.DataLabel}>Size</span>
      <span class={styles.DataValue}>
        {Math.round(cropData.width)} x {Math.round(cropData.height)}
      </span>
    </div>
  </div>
</div>

```

### Context

Use `ImageCropper.Context` to access the cropper API from anywhere inside the root. You get methods like `zoomBy`,
`rotateBy`, and `setZoom`.

**Example: context**

#### React

```tsx
import { ImageCropper } from '@ark-ui/react/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const Context = () => {
  return (
    <div className={styles.Layout}>
      <ImageCropper.Root className={styles.Root}>
        <ImageCropper.Context>
          {(context) => (
            <div className={button.Group}>
              <button className={button.Root} onClick={() => context.zoomBy(-0.1)}>
                <ZoomOutIcon />
              </button>
              <span style={{ fontSize: '0.875rem', padding: '0 0.5rem', minWidth: '3rem', textAlign: 'center' }}>
                {context.zoom.toFixed(1)}x
              </span>
              <button className={button.Root} onClick={() => context.zoomBy(0.1)}>
                <ZoomInIcon />
              </button>
            </div>
          )}
        </ImageCropper.Context>

        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Solid

```tsx
import { For } from 'solid-js'
import { ImageCropper } from '@ark-ui/solid/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const Context = () => {
  return (
    <div class={styles.Layout}>
      <ImageCropper.Root class={styles.Root}>
        <ImageCropper.Context>
          {(context) => (
            <div class={button.Group}>
              <button class={button.Root} onClick={() => context().zoomBy(-0.1)}>
                <ZoomOutIcon />
              </button>
              <span
                style={{ 'font-size': '0.875rem', padding: '0 0.5rem', 'min-width': '3rem', 'text-align': 'center' }}
              >
                {context().zoom.toFixed(1)}x
              </span>
              <button class={button.Root} onClick={() => context().zoomBy(0.1)}>
                <ZoomInIcon />
              </button>
            </div>
          )}
        </ImageCropper.Context>

        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.Root>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper } from '@ark-ui/vue/image-cropper'
import { ZoomIn, ZoomOut } from 'lucide-vue-next'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'
</script>

<template>
  <div :class="styles.Layout">
    <ImageCropper.Root :class="styles.Root">
      <ImageCropper.Context v-slot="context">
        <div :class="button.Group">
          <button :class="button.Root" @click="context.zoomBy(-0.1)">
            <ZoomOut />
          </button>
          <span :style="{ fontSize: '0.875rem', padding: '0 0.5rem', minWidth: '3rem', textAlign: 'center' }">
            {{ context.zoom.toFixed(1) }}x
          </span>
          <button :class="button.Root" @click="context.zoomBy(0.1)">
            <ZoomIn />
          </button>
        </div>
      </ImageCropper.Context>

      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.Root>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper, type UseImageCropperContext } from '@ark-ui/svelte/image-cropper'
  import { ZoomInIcon, ZoomOutIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/image-cropper.module.css'
</script>

<div class={styles.Layout}>
  <ImageCropper.Root class={styles.Root}>
    <ImageCropper.Context>
      {#snippet render(context: UseImageCropperContext)}
        <div class={button.Group}>
          <button type="button" class={button.Root} onclick={() => context().zoomBy(-0.1)}>
            <ZoomOutIcon />
          </button>
          <span style="font-size: 0.875rem; padding: 0 0.5rem; min-width: 3rem; text-align: center;">
            {context().zoom.toFixed(1)}x
          </span>
          <button type="button" class={button.Root} onclick={() => context().zoomBy(0.1)}>
            <ZoomInIcon />
          </button>
        </div>
      {/snippet}
    </ImageCropper.Context>

    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.Root>
</div>

```

### Root Provider

Use `RootProvider` with `useImageCropper` when you need to control the cropper from outside the component tree. Build
custom toolbars or integrate with form state.

**Example: root-provider**

#### React

```tsx
import { ImageCropper, useImageCropper } from '@ark-ui/react/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const RootProvider = () => {
  const imageCropper = useImageCropper()

  return (
    <div className={styles.Layout}>
      <div className={button.Group}>
        <button className={button.Root} onClick={() => imageCropper.setZoom(imageCropper.zoom - 0.1)}>
          <ZoomOutIcon />
        </button>
        <span style={{ fontSize: '0.875rem', minWidth: '3rem', textAlign: 'center' }}>
          {imageCropper.zoom.toFixed(1)}x
        </span>
        <button className={button.Root} onClick={() => imageCropper.setZoom(imageCropper.zoom + 0.1)}>
          <ZoomInIcon />
        </button>
      </div>

      <ImageCropper.RootProvider className={styles.Root} value={imageCropper}>
        <ImageCropper.Viewport className={styles.Viewport}>
          <ImageCropper.Image
            className={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection className={styles.Selection}>
            {ImageCropper.handles.map((position) => (
              <ImageCropper.Handle className={styles.Handle} key={position} position={position}>
                <div />
              </ImageCropper.Handle>
            ))}
            <ImageCropper.Grid className={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid className={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.RootProvider>
    </div>
  )
}

```

#### Solid

```tsx
import { For } from 'solid-js'
import { ImageCropper, useImageCropper } from '@ark-ui/solid/image-cropper'
import { ZoomInIcon, ZoomOutIcon } from 'lucide-solid'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

export const RootProvider = () => {
  const imageCropper = useImageCropper()

  return (
    <div class={styles.Layout}>
      <div class={button.Group}>
        <button class={button.Root} onClick={() => imageCropper().setZoom(imageCropper().zoom - 0.1)}>
          <ZoomOutIcon />
        </button>
        <span style={{ 'font-size': '0.875rem', 'min-width': '3rem', 'text-align': 'center' }}>
          {imageCropper().zoom.toFixed(1)}x
        </span>
        <button class={button.Root} onClick={() => imageCropper().setZoom(imageCropper().zoom + 0.1)}>
          <ZoomInIcon />
        </button>
      </div>

      <ImageCropper.RootProvider class={styles.Root} value={imageCropper}>
        <ImageCropper.Viewport class={styles.Viewport}>
          <ImageCropper.Image
            class={styles.Image}
            src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
            alt="Sample"
          />
          <ImageCropper.Selection class={styles.Selection}>
            <For each={ImageCropper.handles}>
              {(position) => (
                <ImageCropper.Handle class={styles.Handle} position={position}>
                  <div />
                </ImageCropper.Handle>
              )}
            </For>
            <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
            <ImageCropper.Grid class={styles.Grid} axis="vertical" />
          </ImageCropper.Selection>
        </ImageCropper.Viewport>
      </ImageCropper.RootProvider>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { ImageCropper, useImageCropper } from '@ark-ui/vue/image-cropper'
import { ZoomIn, ZoomOut } from 'lucide-vue-next'
import button from 'styles/button.module.css'
import styles from 'styles/image-cropper.module.css'

const imageCropper = useImageCropper()
</script>

<template>
  <div :class="styles.Layout">
    <div :class="button.Group">
      <button :class="button.Root" @click="imageCropper.setZoom(imageCropper.zoom - 0.1)">
        <ZoomOut />
      </button>
      <span :style="{ fontSize: '0.875rem', minWidth: '3rem', textAlign: 'center' }">
        {{ imageCropper.zoom.toFixed(1) }}x
      </span>
      <button :class="button.Root" @click="imageCropper.setZoom(imageCropper.zoom + 0.1)">
        <ZoomIn />
      </button>
    </div>

    <ImageCropper.RootProvider :class="styles.Root" :value="imageCropper">
      <ImageCropper.Viewport :class="styles.Viewport">
        <ImageCropper.Image
          :class="styles.Image"
          src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
          alt="Sample"
        />
        <ImageCropper.Selection :class="styles.Selection">
          <ImageCropper.Handle
            v-for="position in ImageCropper.handles"
            :key="position"
            :class="styles.Handle"
            :position="position"
          >
            <div />
          </ImageCropper.Handle>
          <ImageCropper.Grid :class="styles.Grid" axis="horizontal" />
          <ImageCropper.Grid :class="styles.Grid" axis="vertical" />
        </ImageCropper.Selection>
      </ImageCropper.Viewport>
    </ImageCropper.RootProvider>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { ImageCropper, useImageCropper } from '@ark-ui/svelte/image-cropper'
  import { ZoomInIcon, ZoomOutIcon } from 'lucide-svelte'
  import button from 'styles/button.module.css'
  import styles from 'styles/image-cropper.module.css'

  const imageCropper = useImageCropper()
</script>

<div class={styles.Layout}>
  <div class={button.Group}>
    <button
      type="button"
      class={button.Root}
      onclick={() => imageCropper().setZoom(imageCropper().zoom - 0.1)}
    >
      <ZoomOutIcon />
    </button>
    <span style="font-size: 0.875rem; min-width: 3rem; text-align: center;">
      {imageCropper().zoom.toFixed(1)}x
    </span>
    <button
      type="button"
      class={button.Root}
      onclick={() => imageCropper().setZoom(imageCropper().zoom + 0.1)}
    >
      <ZoomInIcon />
    </button>
  </div>

  <ImageCropper.RootProvider class={styles.Root} value={imageCropper}>
    <ImageCropper.Viewport class={styles.Viewport}>
      <ImageCropper.Image
        class={styles.Image}
        src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
        alt="Sample"
      />
      <ImageCropper.Selection class={styles.Selection}>
        {#each ImageCropper.handles as position}
          <ImageCropper.Handle class={styles.Handle} {position}>
            <div></div>
          </ImageCropper.Handle>
        {/each}
        <ImageCropper.Grid class={styles.Grid} axis="horizontal" />
        <ImageCropper.Grid class={styles.Grid} axis="vertical" />
      </ImageCropper.Selection>
    </ImageCropper.Viewport>
  </ImageCropper.RootProvider>
</div>

```

## API Reference

### Props

<ComponentTypes id="image-cropper" />

### Context

**API:**

| Property | Type | Description |
|----------|------|-------------|
| `zoom` | `number` | The current zoom level of the image. |
| `rotation` | `number` | The current rotation of the image in degrees. |
| `flip` | `FlipState` | The current flip state of the image. |
| `crop` | `Rect` | The current crop area rectangle in viewport coordinates. |
| `offset` | `Point` | The current offset (pan position) of the image. |
| `naturalSize` | `Size` | The natural (original) size of the image. |
| `viewportRect` | `BoundingRect` | The viewport rectangle dimensions and position. |
| `dragging` | `boolean` | Whether the crop area is currently being dragged. |
| `panning` | `boolean` | Whether the image is currently being panned. |
| `setZoom` | `(zoom: number) => void` | Function to set the zoom level of the image. |
| `zoomBy` | `(delta: number) => void` | Function to zoom the image by a relative amount. |
| `setRotation` | `(rotation: number) => void` | Function to set the rotation of the image. |
| `rotateBy` | `(degrees: number) => void` | Function to rotate the image by a relative amount in degrees. |
| `setFlip` | `(flip: Partial<FlipState>) => void` | Function to set the flip state of the image. |
| `flipHorizontally` | `(value?: boolean) => void` | Function to flip the image horizontally. Pass a boolean to set explicitly or omit to toggle. |
| `flipVertically` | `(value?: boolean) => void` | Function to flip the image vertically. Pass a boolean to set explicitly or omit to toggle. |
| `resize` | `(handlePosition: HandlePosition, delta: number) => void` | Function to resize the crop area from a handle programmatically. |
| `reset` | `() => void` | Function to reset the cropper to its initial state. |
| `getCroppedImage` | `(options?: GetCroppedImageOptions) => Promise<string | Blob>` | Function to get the cropped image with all transformations applied.
Returns a Promise that resolves to either a Blob or data URL. |
| `getCropData` | `() => CropData` | 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. |
