Lightbox

A full-screen, browsable image viewer over Radix Dialog. Give it the image list once and any LightboxTrigger opens it at that index. Page between images with arrow buttons, arrow keys and a thumbnail rail.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add lightbox

Usage#

import { Lightbox, LightboxTrigger, type LightboxImage } from "@/components/ui/lightbox"const images: LightboxImage[] = [  { src: "/previews/1.jpg", alt: "Homepage" },  { src: "/previews/2.jpg", alt: "Pricing" },]<Lightbox images={images}>  {images.map((image, i) => (    <LightboxTrigger key={image.src} index={i} className="aspect-[4/3] rounded-xl">      <img src={image.src} alt={image.alt} className="size-full object-cover" />    </LightboxTrigger>  ))}</Lightbox>

Wrap a Gallery tile in a LightboxTrigger with asChild, and give the GalleryItem an action label so it reveals the “See image” pill on hover and opens the viewer on click.

<Lightbox images={images}>  <GalleryMasonry>    {images.map((image, i) => (      <LightboxTrigger key={image.src} index={i} asChild>        <GalleryItem action="See image">          <GalleryImage src={image.src} alt={image.alt} className={i % 2 ? "aspect-[3/2]" : "aspect-[4/5]"} />        </GalleryItem>      </LightboxTrigger>    ))}  </GalleryMasonry></Lightbox>

Single image#

With one image the arrows and the thumbnail rail disappear, so a lone trigger works as a simple click-to-zoom for a single screenshot, with its own pill label.

API reference#

Lightbox

The root. Takes images (an array of { src, alt? }) and renders the viewer. Open state is uncontrolled by default; pass open + onOpenChange to control it, and defaultIndex to set the first image. Built on Radix Dialog, so focus trap, scroll lock, Esc, and exit animations are handled.

LightboxTrigger

Opens the viewer at its index. Renders a <button> with a hover/focus label pill (default “See image”, pass null to hide). Pass asChild to make any tile (for example a GalleryItem) the trigger — then it draws no pill and defers to that element's affordance.

Navigation

The arrow buttons, the ← / → keys, the thumbnail rail, and a swipe/drag on the image itself page between images (looping); the active thumbnail is ringed and a counter sits top-left. They all appear only with more than one image. The image follows the finger while you drag and commits on a firm swipe (by distance or a quick flick), otherwise it springs back to center. The rail is bounded to the screen width and scrolls sideways when the thumbnails outrun it (common on mobile or with many images), keeping the active tile scrolled to center as you page. Click the scrim or press Esc to close. All motion is gated behind motion-safe.

FAQ#

Give each trigger its position in the `images` array via the `index` prop. `LightboxTrigger` calls into the Lightbox to open at that index, so clicking the third tile opens the third image. The viewer then owns navigation from there.