# API reference

Every part, prop and hook. Parts accept all props of their underlying element, plus Base UI's render prop.

```tsx
import { Lightbox, useLightbox } from '@frontendsupply/lightbox'
```

## Root

Holds state. Renders no element.

| Prop | Type | Default | |
| --- | --- | --- | --- |
| `items` | `LightboxItem[]` | — | `{ src, alt?, width?, height?, srcSet?, sizes? }` plus any fields you like |
| `maxZoom` | `number` | `4` | Relative to the fitted size. `1` disables zoom |
| `wheelToZoom` | `boolean` | `false` | Plain wheel zooms instead of panning. Ctrl/⌘ + wheel and pinch always zoom |
| `closeOnBackdropClick` | `boolean` | `true` | |
| `scrollTriggerIntoView` | `'smooth' \| 'instant' \| false` | `'smooth'` | Keep the current item's trigger in view behind the viewer |
| `transition` | Motion `Transition` | spring | The open / close morph |
| `onOpenChange` | `(open: boolean) => void` | | |
| `onIndexChange` | `(index: number) => void` | | |

## Trigger

A `<button>` that opens the viewer at `index`. Its first `<img>` is the morph source; its own box, radius and any
clipping ancestors (scrollers, `overflow: hidden` cards) define the visible crop. While its item is open that `<img>` is
hidden, so the slot reads as "lifted out"; style `[data-active]` for anything else in the trigger (e.g. a "+3" overlay).
Several triggers may share an index (e.g. a hero grid and an all-photos grid); the most visible one wins.

| Prop | Type | |
| --- | --- | --- |
| `index` | `number` | Item to open |
| `render` | `ReactElement \| (props, state) => ReactElement` | Render as another element, e.g. `<a />` |

## Portal, Backdrop, Popup

[Base UI Dialog](https://base-ui.com/react/components/dialog) parts. `Popup` traps focus, closes on `Esc`, returns
focus to the current item's trigger and handles the keyboard shortcuts (your own `onKeyDown` runs first; call
`preventDefault()` to replace a shortcut). Page scroll is locked without reserving a scrollbar gutter, so the overlay
is truly full-bleed and nothing behind it shifts. `Backdrop` and `Popup` get `--lightbox-presence`; `Backdrop`'s
opacity follows it.

## Viewport

The horizontal scroll-snap scroller. With no children it renders `<Slide><Image /></Slide>` for every item.

## Slide

| Prop | Type | |
| --- | --- | --- |
| `index` | `number` | Item shown in this slide |
| `onTap` | `(event: LightboxTapEvent) => void` | Taps on the slide, after the double-tap window when zoom is on (instantly when `maxZoom` is 1) |

```ts
interface LightboxTapEvent {
  index: number
  target: 'image' | 'backdrop'
  x: number // 0 → 1 across the image box
  y: number
  pointerType: string
  preventDefault(): void // skip the default: backdrop → close, touch on image → toggle data-ui-hidden
}
```

Only slides within two of the current index render their children. Non-current slides are `inert`. Slides are the
scroll-snap targets, so don't transform them; animate a child instead (see [Styling](/docs/styling)).

## Image

The morph and zoom target. Defaults `src`, `alt`, `srcSet` and `sizes` from the slide's item; any `<img>` prop works,
so a slide can swap what it shows (the [Stories](/docs/examples/stories) frames do exactly that). While the full image
loads, the trigger's thumbnail is shown in its place.

## Close, Prev, Next, Title, Description

Buttons and labels. `Prev` / `Next` disable themselves at the ends.

## Thumbnails, Thumbnail, ThumbnailIndicator

A thumbnail strip whose indicator travels *with* the swipe: halfway through a swipe it sits halfway between two thumbs,
interpolating both position and size (thumbs may have different widths). The strip glides to keep it centred.

```tsx
<Lightbox.Thumbnails className="flex gap-2 px-[50%]">
  {items.map((item, i) => (
    <Lightbox.Thumbnail key={item.src} index={i} className="h-11 opacity-50 data-active:opacity-100">
      <img src={item.thumb} alt="" className="size-full object-cover" />
    </Lightbox.Thumbnail>
  ))}
  <Lightbox.ThumbnailIndicator className="rounded-md outline-2 outline-offset-2 outline-white" />
</Lightbox.Thumbnails>
```

`Thumbnails` is the scroll container (any positioning works); `Thumbnail` is a button that goes to `index` and gets
`data-active` / `aria-current`; `ThumbnailIndicator` is absolutely positioned and sized over the thumbs every frame —
give it any look: a ring, an underline, a glow.

## useLightbox()

Call it anywhere inside `Root` to build your own UI.

```ts
const {
  items, item, index, count,       // data
  phase, open, zoomed, dragging, uiHidden, // state
  progress,                        // MotionValue: fractional index in view (2.5 = halfway from 3rd to 4th)
  openAt, close, goTo, next, prev, // navigation
  zoomIn, zoomOut, resetZoom,      // zoom
} = useLightbox<MyItem>()
```

`progress` updates every frame of a swipe, drag or glide, so you can build your own dots, progress bars or parallax
with Motion's `useTransform` without re-rendering.
