Skip to main content
Copy for LLM

Image Gallery

<x-wirekit::image-gallery> lays out user-content images in a responsive grid. Clicking a thumbnail opens an accessible lightbox — a focus-trapped dialog that navigates with the arrow keys, closes on Escape, and returns focus to the thumbnail you opened it from. Each image is a <x-wirekit::image> figure, so alt text, lazy loading, and a CLS-safe ratio box come for free.

Pass an array of images, each with a src and an alt (and an optional caption). A signed, ACL-protected download URL works unchanged.

Usage

Gallery with lightbox

Columns and ratio

Control the responsive grid with columns (forwarded to the grid) and the thumbnail shape with ratio:

Three columns, 4/3 thumbnails

Static grid (no lightbox)

Set :lightbox="false" for a plain responsive grid of figures — no dialog, no JavaScript:

Static grid

Per-Item Overlays

Pass an itemOverlay closure to layer a control over each thumbnail — a badge, a per-image report button, or a required content label (a synthetic-media disclosure, a license marker). The closure receives ($item, $i) and returns the markup; it renders as a sibling of the zoom trigger, not inside it, so the thumbnail still opens the lightbox. The overlay wrapper is pointer-events-none — give any interactive control inside it pointer-events: auto so it stays clickable without triggering the zoom. The overlay covers the thumbnail only; the zoom view takes its own, below.

Return a view or an HtmlString for HTML (a plain string is escaped):

Per-item overlay badge
New
New

In real code the closure typically returns a partial view so the overlay can carry its own components and logic:

<x-wirekit::image-gallery
    :images="$photos"
    :item-overlay="fn (array $item, int $i) => view('partials.photo-report', ['photo' => $item, 'index' => $i])"
/>

Overlays in the zoom view

itemOverlay covers the thumbnail only. Pass zoomOverlay to lay markup over the image in the zoom view as well: the same ($item, $i) closure, rendered over the enlarged image and shown only while that image is. It is a separate callback because the two surfaces differ, and an overlay written for a square tile can sit wrong over a full-screen photo. A label that has to travel with the image goes to both:

A label on the thumbnail and in the zoom view
AI-generated
AI-generated

A sharper zoom without a heavier grid

A tile is a few hundred pixels wide and a page carries ten of them. The lightbox is the whole screen. Give each surface its own step of your image ladder with full:

<x-wirekit::image-gallery :images="[
    ['src' => $photo->url(800), 'full' => $photo->url(1600), 'alt' => 'Harbor at dawn'],
    ['src' => $photo->url(800), 'full' => $photo->url(1600), 'alt' => 'The old crane'],
]" />

full is optional and falls back to src, so a gallery that does not use it shows src in the zoom as well. Without it you have to pick one address for both surfaces — the small step leaves the zoom blurry, the large one makes every tile in a feed pay the zoom's resolution.

To let the reader enlarge the image the lightbox shows, add zoomable, and max-zoom for the largest scale. The gallery hands both to its lightbox; how the zoom is used is described under Zoom inside the image.

There is deliberately no preview for this above: two galleries built from different steps of the same ladder look identical on this page, which is the point of the feature rather than something a screenshot can show.

Props

Prop Type Default Description
emptyText string Nothing here yet Sentence shown when images is empty. The empty slot replaces it entirely.
images array [] List of images — each an array with src, alt, and optional caption and full. full is the address the lightbox loads and defaults to src. A plain string is treated as a decorative src.
columns string '2 md:3 lg:4' Responsive grid column spec (forwarded to the grid).
gap string 'md' Grid gap token.
ratio string|null '1/1' Thumbnail aspect-ratio (CLS-safe).
fit string 'cover' object-fit for each thumbnail. 'cover' crops to a uniform grid (right for mixed-orientation sets); 'contain' shows each image whole, letterboxed inside its ratio box — use it when a single portrait shot must not be cropped.
lightbox bool true Enable the click-to-zoom lightbox. false renders a static grid.
itemOverlay Closure|null null Per-item overlay render-callback fn($item, $i). Returns markup layered over thumbnail $i as a sibling of the zoom trigger — a badge, a report control, a synthetic-media label. Thumbnail only. Return a view or HtmlString for HTML. See Per-Item Overlays.
zoomOverlay Closure|null null The zoom view's overlay: the same fn($item, $i) closure, rendered over the enlarged image and shown only while that image is. Ignored when lightbox is false. See Overlays in the zoom view.
zoomable bool false Zoom inside the image the lightbox shows, handed to the lightbox. Ignored when lightbox is false. See Zoom inside the image.
maxZoom int|float 4 The largest scale with zoomable, as a multiple of the fitted size.
scope string|null null Scoped personalization key.

Accessibility

  • An aria-label or aria-labelledby of yours makes the grid of thumbnails a group named by it; the viewer keeps its own name.
  • Each thumbnail is a real <button> with an accessible name ("View image 2: …"), so the gallery is fully keyboard-operable.
  • The lightbox is a role="dialog" aria-modal="true" overlay, focus-trapped with the same helper Modal and Drawer use — focus cannot escape to the page behind it, and it returns to the triggering thumbnail on close.
  • Every image carries its alt; decorative images pass alt="".

Keyboard Interaction

Key Action
Enter / Space Open the lightbox on the focused thumbnail
Escape Close the lightbox (focus returns to the thumbnail)
← Previous image
→ Next image
Tab Move between the lightbox controls (focus stays trapped inside)
+ - 0 Zoom in, out and back, with zoomable; the lightbox's keys list the rest

Updated in WireKit v2.66.0 (2026-10-13)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store