Skip to main content
Copy for LLM

Image Editor

<x-wirekit::image-editor> draws a crop over an image. The reader drags the crop or one of its eight handles, moves and sizes it with the arrow keys, or types its place and size into four fields. The image itself stays as it is: the value is the crop, a rectangle in percent of the image, and your application decides what to do with it, such as show a phone's crop of a desktop image or resize the file on the server.

Basic Usage

Give the image and its size in pixels:

A Crop Over a Room

Drag the crop or its handles, or use the arrow keys: they move the crop, and with Shift they change its size.

A living room with a sofa, a floor lamp and a rug
Crop in percent

Without a value the crop is the whole image. The size holds the crop to its ratio and to its smallest side before the file arrives; without width and height the editor waits for the image to load and takes its natural size.

A Fixed Ratio

ratio holds the crop to a width over a height in pixels, 9/16 for an upright phone image, 1/1 for a square or 16/9 for a wide one. Without a value the crop is the largest one of that ratio, centered, and a value of another ratio keeps its middle while its longer side shrinks until it fits:

A Phone's Crop of a Desktop Image

Drag the crop or its handles, or use the arrow keys: they move the crop, and with Shift they change its size.

A living room with a sofa, a floor lamp and a rug
Crop in percent

A corner of a crop with a ratio takes the larger of the two sizes the pointer asks for, and an edge sizes its own side while the other side follows around its middle. At the edge of the image both stop together.

The Value

The crop is an array in percent of the image, with one decimal:

Key Description
x, y Where the crop's top left corner stands, from the image's top left corner
w, h The crop's width and height

Percent is the unit image hotspots place their markers in, so a crop and a marker are positions on the same image. Pushery\WireKit\Support\ImageCrop converts both ways an application needs:

use Pushery\WireKit\Support\ImageCrop;

// 1. The crop in the pixels of a 1600 × 1000 image, for a resize on the server
$pixels = ImageCrop::pixels($crop, 1600, 1000);

// 2. A marker of the whole image as a marker of the crop, or null where it falls outside
$marker = ImageCrop::point($crop, ['x' => 24, 'y' => 58, 'label' => 'Sofa Bergen']);

// 3. Every marker inside the crop, each with its other keys, in their order
$markers = ImageCrop::points($crop, $look->hotspots);

A point of the crop keeps every key of the marker it came from, so its label and its target stay with it.

With Livewire

Bind the crop with wire:model. The property receives the crop after a drag ends, after each key press and after a field changes, and a drag sends nothing while it runs, also with wire:model.live:

<?php

namespace App\Livewire;

use App\Models\Look;
use Illuminate\Contracts\View\View;
use Livewire\Component;

class LookCrop extends Component
{
    public Look $look;

    /** @var array{x: float, y: float, w: float, h: float}|null */
    public ?array $crop = null;

    public function mount(Look $look): void
    {
        // 1. The crop the look keeps, or none for the largest crop of the ratio
        $this->look = $look;
        $this->crop = $look->phone_crop;
    }

    public function save(): void
    {
        // 2. Four numbers between 0 and 100
        $this->validate([
            'crop.x' => ['required', 'numeric', 'between:0,100'],
            'crop.y' => ['required', 'numeric', 'between:0,100'],
            'crop.w' => ['required', 'numeric', 'between:0,100'],
            'crop.h' => ['required', 'numeric', 'between:0,100'],
        ]);

        // 3. Only the rectangle is kept; the image stays as it was uploaded
        $this->look->update(['phone_crop' => $this->crop]);
    }

    public function render(): View
    {
        return view('livewire.look-crop');
    }
}
<div>
    <x-wirekit::image-editor
        :src="$look->image_url"
        :alt="$look->image_alt"
        width="1600"
        height="1000"
        ratio="9/16"
        wire:model="crop"
    />

    <x-wirekit::button wire:click="save">Save</x-wirekit::button>
</div>

The model above casts phone_crop to array. In an Alpine component of your own, x-model binds the same crop. See An x-model on a WireKit component.

Props

Prop Type Default Description
src string — The image's address. Without one nothing renders, and it is reported
alt string '' The image's text alternative
width int|null null The image's width in pixels
height int|null null The image's height in pixels. With width it holds the crop to its ratio and its smallest side before the image loads
value array|null null The crop to start with where no binding hands it over. See The Value
ratio string|null null The crop's width over its height in pixels, such as 9/16, or a number; null for a free crop. Another text is reported
min int 16 The crop's smallest side, in pixels of the image
label string|null "Crop" The crop's name
help string|null "Drag the crop or its handles, or use the arrow keys: they move the crop, and with Shift they change its size." The sentence above the image, as text or as markup, which the crop reads as its description
scope string|null null Scoped personalization key

Events

Event Detail When
wirekit:image-editor-change { crop } The reader moved or sized the crop: when a drag ends, on a key press, a field's change or the reset

Accessibility

  • Dragging is never the only way to crop (WCAG 2.5.7). The crop takes the focus and moves with the arrow keys, and four number fields give its place and size in percent.
  • The crop is a named group, described by its place and size in words and by the sentence above the image, and a status region says the crop after every change.
  • The handles and the dimmed image around the crop are hidden from a screen reader: the crop and the fields are the way to it, and the image keeps its alt.
  • A handle is a target of 24 pixels, 44 on a touch screen, a square on a surface of its own inside a dark edge, and the crop's frame is a dark edge inside a light one, so both show on any image (WCAG 1.4.11). On a touch screen the image keeps half a touch target of space above and below it, so a handle at its edge takes no tap meant for a field.
  • On a right-to-left page the fields and the buttons mirror; the crop does not, since it is the same rectangle of the image in either direction.

Keyboard Interaction

Key Action
Tab Moves to the crop, then through the fields and the reset button
← → ↑ ↓ On the crop, moves it by 1 %
Shift + arrow On the crop, sizes it from its right or bottom edge: → and ↓ grow it, ← and ↑ shrink it
Alt + arrow Moves or sizes it by a tenth of that

Updated in WireKit v2.70.0 (2026-10-31)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store