Skip to content
Devix Open Source

Guide

React, Vue, Svelte and a plain page

Every wrapper is a thin layer over the same DOM core, so all of them behave identically.

React

import { useRef } from 'react';
import { ImageCropper } from '@devix-labs/image-cropper/react';
import '@devix-labs/image-cropper/styles.css';

function AvatarField() {
  const cropper = useRef(null);

  const save = async () => {
    const body = new FormData();
    body.append('avatar', await cropper.current.toFile());
    await fetch('/profile/avatar', { method: 'POST', body });
  };

  return (
    <>
      <ImageCropper ref={cropper} name="avatar" aspect={1} mode="fixed" round maxWidth={512} />
      <button onClick={save}>Save</button>
    </>
  );
}

The ref gives you toBlob(), toFile(), reset(), clear() and instance. Anything that is not an option — name, className, required, aria-* — goes to the input. Changing aspect applies it; the rest are read at mount, so pass a key to rebuild.

To follow the crop as it moves, use onChange; to get a picture on every change, set live and use onCrop.

Vue

<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import { imageCropper } from '@devix-labs/image-cropper/vue';
import '@devix-labs/image-cropper/styles.css';

const host = ref(null);
let picker;

onMounted(() => {
  picker = imageCropper(host.value, { aspect: 1, mode: 'fixed', round: true, maxWidth: 512 });
});
onBeforeUnmount(() => picker?.destroy());

defineExpose({ toFile: () => picker.toFile() });
</script>

<template>
  <input ref="host" type="file" name="avatar">
</template>

Svelte

<script>
  import { cropper } from '@devix-labs/image-cropper/svelte';
  import '@devix-labs/image-cropper/styles.css';

  let picker;
</script>

<input type="file" name="avatar"
       use:cropper={{ aspect: 1, round: true, onPick: () => {} }}
       bind:this={picker}>

A web component

<script type="module" src="https://devix.pk/cdn/oss/image-cropper@1.0.0/element.js"></script>

<dx-image id="avatar" name="avatar" aspect="1" mode="fixed" round max-width="512"></dx-image>

<script type="module">
  const blob = await document.querySelector('#avatar').toBlob();
</script>

Attributes: name, src, aspect, mode, round, max-width, max-height, quality. It exposes .picker and .toBlob(), and fires crop.

A plain page, from the CDN

<link rel="stylesheet" href="https://devix.pk/cdn/oss/image-cropper@1.0.0/image-cropper.min.css">
<script src="https://devix.pk/cdn/oss/image-cropper@1.0.0/image-cropper.min.js"></script>
<script>
  const picker = DevixImageCropper.createImageCropper(document.querySelector('#avatar'), { aspect: 1 });
</script>

Laravel

<form id="profile" method="post" action="{{ route('avatar.store') }}">
  @csrf
  <label for="avatar">Profile picture</label>
  <input id="avatar" name="avatar" type="file">
  <button>Save</button>
</form>

<script type="module">
  import { createImageCropper } from '/cdn/oss/image-cropper@1.0.0/index.js';

  const picker = createImageCropper(document.querySelector('#avatar'), {
    aspect: 1, mode: 'fixed', round: true, maxWidth: 512, type: 'image/webp',
  });

  document.querySelector('#profile').addEventListener('submit', async (event) => {
    event.preventDefault();
    const body = new FormData(event.target);
    body.set('avatar', await picker.toFile('avatar'));
    await fetch(event.target.action, { method: 'POST', body });
  });
</script>
$request->validate(['avatar' => ['required', 'image', 'dimensions:min_width=64', 'max:2048']]);
$path = $request->file('avatar')->store('avatars', 'public');

A 512-pixel WebP from a 12-megapixel phone photograph is a few tens of kilobytes, so the upload is quick and the server has nothing left to resize.

Server rendering

Nothing touches window at import time, so importing the package on a server is safe in Next.js, Nuxt, SvelteKit and Astro. The widget is built when the component mounts.

Updated 15 Sep 2026