Skip to main content
WireKit
Copy for LLM

Lightbox

<x-wirekit::lightbox> is a reusable, accessible zoom overlay for media — photos, video, and embeds. It is a focus-trapped role="dialog" that opens over the page, steps through its items with the arrow keys, closes on Escape, and returns focus to whatever opened it. It is the same overlay <x-wirekit::image-gallery> uses internally, exposed on its own so you can drive it from any trigger and any layout — a single thumbnail, a "play" button, a link in prose.

Pass an array of items, each with a src, an alt, an optional caption, and an optional type (image — the default — video, or embed). Then open it two ways: a trigger inside the component calls openAt(index) directly, or any control anywhere on the page dispatches a wirekit-lightbox-open event with a matching name.

Usage

Put a trigger in the default slot — it shares the lightbox's Alpine scope, so it can call openAt(0) to open at the first item:

Open a single image

A thumbnail as the trigger

Any element in the slot works as the trigger. Here a clickable <x-wirekit::image> thumbnail opens the full-size view:

Click the thumbnail

Multiple items

Give the lightbox several items and it gains prev / next controls plus arrow-key navigation. Any control in the slot can jump straight to a specific index:

Step through a set

Large images scale to fit

Whatever the source dimensions, the media fills most of the screen with its aspect ratio intact — it never overflows or forces the page to scroll:

  • A very wide panorama is capped to ~90% of the viewport width (max-w-[90vw]); its height scales down to match.
  • A very tall image is capped to ~85% of the viewport height (max-h-[85vh]); its width scales down to match.

object-contain keeps both from stretching. Open each below — a 21:9 panorama and a 9:21 column both land fully inside the viewport, using the available space:

Wide and tall images both fit

Lazy loading and the loading spinner

Off-screen slides are not downloaded up front. Each image is loading="lazy", so only the slide you are viewing (and one you navigate to) fetches its full-size source — a gallery of large photos does not pay for all of them on open. Embeds are lazy too; videos preload only their metadata.

While a large image is still downloading, the viewer shows a loading spinner in its place, then fades the image in once it has loaded. There is nothing to configure — it is automatic for every image item. (Open the wide or tall image above on a throttled connection to see the spinner before the image resolves.)

Video and embeds

Set type to video for a native <video> player, or embed for an <iframe> (an embedded map, a video host, a document viewer). A video item also takes an optional poster still, shown before the clip paints its first frame — so the surface is not blank while it buffers. Here a poster image with a play affordance opens the clip in the lightbox:

Video in a lightbox

Mix types freely in one lightbox — the viewer, focus trap, and keyboard model are identical:

<x-wirekit::lightbox name="product-media" :items="[
    ['src' => $photo->url, 'alt' => 'Product photo', 'caption' => 'Front view'],
    ['src' => $clip->url, 'type' => 'video', 'poster' => $clip->posterUrl, 'alt' => 'Product in use', 'caption' => 'In use'],
    ['src' => $tourUrl, 'type' => 'embed', 'alt' => '360° tour', 'caption' => '360° tour'],
]">
    <x-wirekit::button x-on:click="openAt(1)">Watch the clip</x-wirekit::button>
</x-wirekit::lightbox>

Loop on or off

By default prev / next wrap around at the ends. Set :loop="false" and the controls stop — the prev control is disabled on the first item, next on the last:

Stops at the ends

Captions off

Captions render under the media by default. Turn them off with :show-captions="false" for a clean, chrome-free viewer:

No captions

A longer caption

A caption is not limited to a short label. A full sentence wraps onto multiple lines, stays centered under the media, and never runs wider than the image — good for a credit line, a description, or a note about what the reader is looking at:

A multi-line caption

Backdrop color per instance

The backdrop uses the themeable --color-wk-overlay token by default. Override it per instance with overlay — any CSS color, so you can dim harder for photos or tint the surround to match a brand:

A darker backdrop

Open from anywhere (event-driven)

A control does not have to live inside the lightbox. Give the lightbox a stable name, then dispatch a wirekit-lightbox-open event with that name and the target index from any element on the page — a table row, a card, a menu item:

{{-- The lightbox lives once, near the end of the page --}}
<x-wirekit::lightbox name="product-media" :items="$media" />

{{-- Any control, anywhere, opens it at a chosen index --}}
<button
    type="button"
    x-on:click="$dispatch('wirekit-lightbox-open', { name: 'product-media', index: 3 })"
>
    Open the fourth image
</button>

The event carries { name, index }; only the lightbox whose name matches responds. This keeps one overlay instance for many triggers instead of nesting a lightbox per thumbnail.

Design tokens

The lightbox styles itself entirely from design tokens, so it re-themes with the rest of WireKit. Retint these to restyle it (see theming):

Token Drives
--color-wk-overlay The backdrop color (default — override per instance with overlay).
--z-wk-modal The stacking level, above page content.
--radius-wk-md The corner radius on the media and controls.
--shadow-wk-lg The elevation of the media surface.
--shadow-wk-md The elevation of the prev / next / close controls.
--color-wk-bg-elevated The control button background.
--color-wk-text The control icon color.
--color-wk-ring + --ring-wk-width The keyboard focus ring on the controls.
--color-wk-bg The caption text color (light-on-dark).
--text-wk-sm The caption text size.
--space-wk-md / --space-wk-sm Overlay padding, control insets, and the media-to-caption gap.
--opacity-wk-disabled The dimmed prev / next control when loop is off at an end.

Props

Prop Type Default Description
name string|null auto Identifier this instance answers to for the wirekit-lightbox-open event. Auto-generated when omitted.
items array [] The slides — each an array with src, alt, optional caption, optional type (image / video / embed), and an optional poster (video-only still shown before the clip paints). A plain string is a decorative image src.
loop bool true Whether prev / next wraps around at the ends. false disables the control at each end.
showCaptions bool true Render each item's caption under the media.
overlay string|null null Backdrop color / opacity for this instance (any CSS color). Null → the --color-wk-overlay token.
scope string|null null Scoped personalization key.

Accessibility

  • The overlay is a role="dialog" aria-modal="true" surface, focus-trapped with the same helper Modal and Drawer use — focus cannot reach the page behind it, and it returns to the trigger on close.
  • Every prev / next / close control has an accessible name; the media carries its alt (decorative items pass alt="").
  • Motion (the fade / scale entrance) respects prefers-reduced-motion — users who ask for less motion get an instant open.
  • Provide a real, keyboard-focusable trigger (a <button> or <x-wirekit::button>) so the lightbox is reachable without a pointer.

Keyboard Interaction

Key Action
Enter / Space Open the lightbox from the focused trigger
Escape Close the lightbox (focus returns to the trigger)
Previous item
Next item
Tab Move between the lightbox controls (focus stays trapped inside)

Was this page helpful?

Thanks — that helps.

Voting requires cookies or local storage. What we store