<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Lightbox

Thumbnails that open a full-screen image viewer. Use it for proof screenshots, a listing's photos or before-and-after pairs: pressing a thumbnail opens the viewer on that image. It shows the image as large as the screen allows, uncropped, with its title and caption under it and Close beside the position ("2 of 4") at its top. Step through the images with the arrow keys, the Previous and Next buttons, a trackpad or a swipe, and go to the first or the last with Home or End. The position changes as soon as a key or button chooses an image, and is announced politely, once, when the image arrives. With one image the viewer shows neither the position nor Previous and Next. Escape, Close or a press outside closes it, and focus goes to the thumbnail of the image last shown. The viewer grows from the thumbnail pressed and slides between images; with reduced motion it fades and jumps instead. The thumbnails are fetched as they are drawn, not as they near the screen, so pictures below the fold are there when the reader scrolls to them. Pressing a thumbnail starts fetching its full image before the viewer opens (a finger's or pen's tap, as it lifts without having moved, so a scroll that starts on a thumbnail fetches nothing), and the viewer fetches the image shown first, then the ones a step either side, then the rest. On a phone it runs edge to edge, and an image drawn smaller than its own size zooms to that size with a double-tap or the zoom button, or as far as two fingers pinch it; zoomed, a swipe moves around the image rather than to the next one.

```tsx
import { Lightbox } from "@/components/ouro/lightbox/lightbox";
```

## Example

```tsx
<Lightbox
  label="Kitchen photos"
  images={[
    { src: "/photos/before.jpg", alt: "The old kitchen, with dark cabinets", title: "Before" },
    { src: "/photos/after.jpg", alt: "The new kitchen, with white cabinets", title: "After", caption: "Six weeks later" },
  ]}
/>
<Lightbox
  label="Kitchen photos"
  images={photos}
  open={open}
  index={index}
  onOpenChange={setOpen}
  onIndexChange={setIndex}
/>
```

## Usage

For a chat message's pictures, pass `layout="mosaic"`: one frame that shows one picture at its own shape, two side by side, three as one large and two small, and four or more as a 2 by 2 grid with "+N" on the last, at most 25rem wide and 17.5rem tall. Pass `fullScreen` for a viewer that covers the whole screen with no frame. An animated image's `still` frame shows in its thumbnail when the user asks for reduced motion.

To open the viewer from thumbnails you lay out yourself (a before-and-after pair, a mark on a map), pass `open` with `onOpenChange`, the image as `index`, and follow the user with `onIndexChange`: it then draws no thumbnails, and focus goes back to whatever had it, or to the element `returnFocus` gives for the image last shown, such as its thumbnail.

## Use instead when

- Use `Carousel` instead to show the images in the page one at a time, and `Dialog` when the viewer holds more than images.

## Props

The props the modes share. Type: `LightboxProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `closeLabel` | `string` | `"Close"` | The close button's accessible name. |
| `fullScreen` | `boolean` |  | The viewer covers the whole screen at every width, with no frame around it, rather than sitting in a dialog with the page around it. Use it where pictures are the point, as in a chat's photos. |
| `images` (required) | `readonly LightboxImage[]` |  | The images, in order. The viewer steps through them, and the thumbnails show them in a grid. |
| `label` (required) | `string` |  | Names the viewer for screen readers, with the position ("Kitchen photos, 2 of 4"), and shows at its top, such as "Kitchen photos". With thumbnails it names their list too. It is always required. |
| `loop` | `boolean` |  | Next on the last image goes back to the first, and Previous on the first goes to the last. |
| `nextLabel` | `string` | `"Next image"` | The Next button's accessible name. |
| `onIndexChange` | `((index: number) => void)` |  | Called with the image's index when the user moves to another image in the viewer, once per change. |
| `originalLabel` | `string` | `"Open original"` | The text of the link to an image's `href`. |
| `positionLabel` | `string` | `"{n} of {total}"` | The position the viewer shows and announces when it changes: `{n}` is the image's number and `{total}` the number of images. |
| `previousLabel` | `string` | `"Previous image"` | The Previous button's accessible name. |
| `zoomLabel` | `string` | `"Zoom"` | The zoom button's accessible name. It shows while the image is drawn smaller than its own size, and zooms it to that size, as a double-tap or a pinch does. |

## Modes

`LightboxProps` is a union: pass the props of exactly one mode. Mixing them is a type error.

### Controlled: `LightboxControlledProps`

A controlled Lightbox: the viewer alone, which you open from thumbnails you lay out yourself.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `index` | `number` | `0` | The image the viewer shows, by its place in `images`. It opens on it, and moves to it when you change it while open; update it in `onIndexChange` to follow the user. |
| `onOpenChange` (required) | `(open: boolean) => void` |  | Called with `false` when the user closes the viewer (Close, Escape, a press outside). |
| `open` (required) | `boolean` |  | Whether the viewer is open. Focus goes back to whatever had it when the viewer opened, unless `returnFocus` gives another element, and the viewer grows from that element. Keep the Lightbox mounted and change `open`: one that mounts already open after a press or a key appears and leaves without its transitions, and says so in development. |
| `returnFocus` | `((index: number) => HTMLElement \| null \| undefined)` |  | Where focus goes as the viewer closes, given the index of the image last shown: return that image's thumbnail in your layout, so the user is back where they were looking. Return nothing to give focus back to what had it when the viewer opened. |

Not in this mode (typed `never`):

- `className`: Not with `open`: lay out your own thumbnails instead, since a controlled Lightbox draws the viewer alone.
- `layout`: Not with `open`: a controlled Lightbox draws no thumbnails to lay out.

### With thumbnails: `LightboxWithThumbnailsProps`

A Lightbox that draws its own thumbnails, which open the viewer.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  | Layout only, for the thumbnails: width and margins (`max-w-2xl`, `mt-6`). Appearance classes warn in development. |
| `layout` | `"grid" \| "mosaic"` | `"grid"` | How the thumbnails sit. `grid`: as many 4:3 thumbnails in a row as fit. `mosaic`: one frame, as a chat shows the pictures in a message: one picture at its own shape (`width` and `height`), two side by side, three as one large and two small, four or more as a 2 by 2 grid with "+N" on the last; the viewer still steps through them all. The frame is as big as a chat shows pictures: at most 25rem wide and 17.5rem tall, and never wider than its column, so a picture never crowds the words around it; `className` gives it another width (`max-w-none`). |

Not in this mode (typed `never`):

- `index`: Not with thumbnails. Pass `open` and `onOpenChange` too to open the viewer from your own layout.
- `onOpenChange`: Not with thumbnails. Pass `open` and `onOpenChange` too to open the viewer from your own layout.
- `open`: Not with thumbnails. Pass `open` and `onOpenChange` too to open the viewer from your own layout.
- `returnFocus`: Not with thumbnails: focus goes to the thumbnail of the image last shown.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `fullscreen`: Not a prop. Lightbox uses `fullScreen`: `<Lightbox fullScreen>`.
- `slides`: Not a prop. Pass the images as `images`: `<Lightbox images={[{ src, alt }]}>`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`).

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `LightboxImage`

One image in a `Lightbox`.

```ts
interface LightboxImage {
  /** The full-size image's address, shown in the viewer. */
  src: string;
  /** Describes the image for screen readers, on its thumbnail and in the viewer. */
  alt: string;
  /** A smaller file for the thumbnail. @default src */
  thumbnail?: string | undefined;
  /** A short title under the image in the viewer, shown as given. */
  title?: ReactNode;
  /**
   * A line under the title in the viewer, shown as given: Lightbox adds nothing to it.
   */
  caption?: ReactNode;
  /** The original file, or a page about the image: the viewer links to it in a new tab. */
  href?: string | undefined;
  /**
   * The image's own width and height, in any unit. With `layout="mosaic"` and one image, its
   * thumbnail keeps this shape (between 3:4 and 3:1), so the page holds its place before it loads.
   */
  width?: number | undefined;
  /** The image's own height; see `width`. */
  height?: number | undefined;
  /**
   * A still frame of an animated image (a GIF), which its thumbnail shows instead when the user
   * asks for reduced motion. The viewer still plays the image, since opening it asks to see it.
   */
  still?: string | undefined;
}
```
