Lightbox

Getting started

View as Markdown

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

Install

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 (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.

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

<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 parts, so you get focus trapping, scroll locking, Esc to close and focus return for free.

Tips