# Getting started

A headless React lightbox with native-feeling gestures. Built on Base UI and Motion. Ships no CSS.

## Install

```bash
pnpm add @frontendsupply/lightbox @base-ui/react motion
```

`react` and `react-dom` 19+ are peer dependencies too. Swiping is native scroll-snap; mouse dragging comes from
[Blossom Carousel](https://www.blossom-carousel.com) (a dependency, with its small stylesheet imported for you).

## Your first lightbox

Give `Root` your items, wrap each thumbnail in a `Trigger`, and render the overlay. `Viewport` without children renders one
zoomable slide per item.

```tsx
import { Lightbox } from '@frontendsupply/lightbox'
import { landscapes } from '../../data/photos'

const photos = landscapes.slice(0, 6)

export default function Basic() {
  return (
    <Lightbox.Root items={photos}>
      <div className="grid grid-cols-3 gap-2">
        {photos.map((photo, i) => (
          <Lightbox.Trigger
            key={photo.src}
            index={i}
            className="overflow-hidden rounded-lg bg-neutral-100 dark:bg-neutral-900"
          >
            <img src={photo.thumb} alt={photo.alt} className="aspect-square size-full object-cover" />
          </Lightbox.Trigger>
        ))}
      </div>

      <Lightbox.Portal>
        <Lightbox.Backdrop className="z-50 bg-black" />
        <Lightbox.Popup className="z-50 outline-none">
          <Lightbox.Viewport />
          <Lightbox.Close className="absolute top-4 right-4 rounded-full bg-white/15 px-4 py-1.5 text-sm text-white backdrop-blur">
            Close
          </Lightbox.Close>
        </Lightbox.Popup>
      </Lightbox.Portal>
    </Lightbox.Root>
  )
}
```

That's the whole API surface for the common case. Everything else — counters, captions, thumbnails, arrows — is your own UI
reading `useLightbox()`.

## Anatomy

```tsx
<Lightbox.Root items={items}>
  <Lightbox.Trigger index={0} />           {/* as many as you like, several per index is fine */}

  <Lightbox.Portal>
    <Lightbox.Backdrop />
    <Lightbox.Popup>
      <Lightbox.Viewport>                   {/* native scroll-snap scroller */}
        <Lightbox.Slide index={0}>          {/* padding here = breathing room around the image */}
          <Lightbox.Image />                {/* the morph + zoom target */}
        </Lightbox.Slide>
      </Lightbox.Viewport>
      <Lightbox.Close />
      <Lightbox.Prev />
      <Lightbox.Next />
    </Lightbox.Popup>
  </Lightbox.Portal>
</Lightbox.Root>
```

`Portal`, `Backdrop`, `Popup`, `Close`, `Title` and `Description` are [Base UI Dialog](https://base-ui.com/react/components/dialog)
parts, so you get focus trapping, scroll locking, `Esc` to close and focus return for free.

## Tips

- **Pass `width` and `height`** on items when you know them. The viewer can then lay out before the full image loads. Without
  them it falls back to the thumbnail's natural size.
- **Thumbnails are placeholders.** While the full image downloads, the viewer shows the trigger's `<img>` — already in cache — in
  its place, so the morph never shows an empty box.
- **The morph reads your CSS.** `object-fit`, `object-position` and `border-radius` of the thumbnail are measured at open and
  close time. Change them freely.
- **Next steps:** [Gestures](/docs/gestures), [Styling](/docs/styling), the [API reference](/docs/api), or steal an example:
  [Feed](/docs/examples/feed), [Listing](/docs/examples/listing), [Stories](/docs/examples/stories).
