Skip to main content
Copy for LLM

Image Hotspots Editor

<x-wirekit::image-hotspots-editor> is where the points of an image with hotspots are made. A click on the image sets a point, dragging moves it, and the list beside the image holds every point with a number field for X and Y, in the order Tab later reaches the markers. Its value is the list <x-wirekit::image-hotspots> takes as markers, so what the editor writes is what the page shows.

Basic Usage

Give the image, its size in pixels and the points to start with:

Two Points on a Room

Click the image to set a point. Drag a point or use the arrow keys to move it.

A living room with a sofa, a floor lamp and a rug

A point without a label is named after its place in the list, "Point 3". The editor carries every key of a point along as it is, a label, a target or anything else the page keeps on it, and writes only the position.

With Livewire

Bind the list with wire:model. The property receives the whole list after every change, and the same array is what image hotspots take. Here a Look keeps its points in a JSON column cast to array:

<?php

namespace App\Livewire;

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

class LookEditor extends Component
{
    public Look $look;

    /** @var list<array<string, mixed>> */
    public array $hotspots = [];

    public function mount(Look $look): void
    {
        $this->look = $look;
        $this->hotspots = $look->hotspots ?? [];
    }

    public function save(): void
    {
        $this->validate([
            'hotspots.*.x' => ['required', 'numeric', 'between:0,100'],
            'hotspots.*.y' => ['required', 'numeric', 'between:0,100'],
        ]);

        $this->look->update(['hotspots' => $this->hotspots]);
    }

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

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

With wire:model the list waits for the next request, here the one Save sends. wire:model.live sends every change at once: each point set or deleted, each press of an arrow key, and a drag once it is let go, not during it. wire:model.live.change sends when a point is committed in the same way, and wire:model.live.blur when the reader leaves the editor.

In an Alpine component of your own, x-model binds the same list. See An x-model on a WireKit component.

A File for the Phone

Where the page shows another file on the phone, give it as phone-src with its size. A switch above the image then edits either file, and every point takes a position of its own on the phone's, phoneX and phoneY. A point that was never moved on the phone stands where the desktop puts it:

A Desktop and a Phone File

Click the image to set a point. Drag a point or use the arrow keys to move it.

A living room with a sofa, a floor lamp and a rug A living room with a sofa, a floor lamp and a rug

The Fields of a Point

What a point leads to is the page's own question: a product, a category, a hint. The editor tells the page which point the reader is on, and the page shows its fields for that point. When the reader selects a point, sets one or adds one, the editor sends wirekit:image-hotspots-editor-select, and it bubbles. Its detail carries index and point, a copy of the point:

<x-wirekit::image-hotspots-editor
    :src="$look->image_url"
    :alt="$look->image_alt"
    width="1600"
    height="1000"
    wire:model="hotspots"
    x-on:wirekit:image-hotspots-editor-select="$wire.set('editing', $event.detail.index)"
/>

@if ($editing !== null && isset($hotspots[$editing]))
    <x-wirekit::input label="Name of the point" wire:model="hotspots.{{ $editing }}.label" />
@endif

The component above needs a public ?int $editing = null; for this. The field and the editor are bound to the same list, so the field writes the name of the point the editor shows.

Points

Each point is an array, the same shape as the markers of image hotspots:

Key Type Description
x, y int|float The position on the desktop file, in percent of the image's width and height from its top left corner. The editor writes them with one decimal, between 0 and 100
phoneX, phoneY int|float The position on the phone's file. The editor writes them once a point is moved on the phone, and a new point set on the phone gets both pairs
label string The point's name, in the list and as the name of its marker. A new point gets an empty one
any other key Carried along as it is: a target, an href, an image

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, which with height reserves its box before the file arrives
height int|null null The image's height in pixels
phone-src string|null null The phone's file. See A File for the Phone
phone-width int|null null The phone file's width in pixels
phone-height int|null null The phone file's height in pixels
value array [] The points to start with where no binding hands them over. See Points
label string|null "Points" The name of the list of points
help string|null "Click the image to set a point. Drag a point or use the arrow keys to move it." The sentence above the image, as text or as markup, which every marker reads as its description
scope string|null null Scoped personalization key

Events

Event Detail When
wirekit:image-hotspots-editor-select { index, point } The reader selected a point, set one with a click or added one with the button

Accessibility

  • Dragging is never the only way to place a point (WCAG 2.5.7). A selected marker moves with the arrow keys, and every row of the list has a number field for X and Y.
  • A point can be set without a pointer (WCAG 2.1.1): "Add a point" sets one in the middle of the image, and the focus goes to its marker, so the arrow keys move it at once.
  • Every marker is a <button> named after its point, pressed while its point is selected, and described by the sentence above the image.
  • The list is named by label, each row starts with the number of its point, and each of its buttons and fields is named after the point: "Move Sofa Bergen up", "Sofa Bergen, X in percent".
  • A status region reads what changed: the position after a move, a point added, a point deleted, the new place of a row.
  • The focus stays with the reader through every change that takes its element away. A deleted point hands it to the point that takes its place, a row moved up or down keeps it on its arrow, and a list that is left empty hands it to "Add a point".
  • A marker measures --size-wk-sm, 2rem, and takes a 44 pixel area on a touch screen. The buttons of a row grow to 44 pixels there.

Keyboard Interaction

Key Action
Tab Moves between the markers, then through the rows of the list and "Add a point"
Enter / Space On a marker or a name in the list, selects the point
← → ↑ ↓ On a marker, moves the point by 1 %
Shift + arrow On a marker, moves the point by 10 %
Delete / Backspace On a marker, deletes the point

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