Lightbox primitives API
Advanced lightbox building blocks for custom markup.
The built-in lightbox is a small set of primitives composed together. Nuxt
applications import them from @lupinum/nuxt-photo/app. Plain Vue applications
import them from @lupinum/vue-photo.
Primitives give you control, not automatic accessibility. The default composition handles focus, labels, keyboard navigation, and announcements; custom compositions need their own accessibility review.
import {
LightboxProvider,
LightboxRoot,
LightboxOverlay,
LightboxViewport,
LightboxSlide,
LightboxControls,
LightboxCaption,
} from '@lupinum/nuxt-photo/app'Composition overview
The primitives have a strict nesting order:
<LightboxProvider :photos="photos"> ← provides context for triggers + primitives
<PhotoTrigger /> ← thumbnail trigger(s)
<LightboxRoot> ← teleports + renders only while open
<LightboxOverlay /> ← backdrop + self-click-to-close
<LightboxViewport> ← gestures, embla container
<LightboxSlide> ← one photo's frame, zoom, pan
</LightboxViewport>
<LightboxControls> ← next/prev/close buttons, a11y live region
<LightboxCaption> ← caption/description text
</LightboxRoot>
</LightboxProvider>Primitive root elements forward normal Vue attributes where the component has a single stable root. For components with internal wrappers, use the documented class props and slots instead of assuming every attribute lands on an outer element.
<LightboxProvider>
Provides the lightbox context to its descendants.
<LightboxProvider :photos="photos">
<!-- everything else -->
</LightboxProvider>Props:
| Prop | Type | Description |
|---|---|---|
photos | PhotoItem | PhotoItem[] | Photo collection for triggers and lightbox. |
transition | LightboxTransitionOption | FLIP/fade transition mode. |
minZoom | number | Minimum zoom scale for click/keyboard zoom. |
imageAdapter | ImageAdapter | Per-provider image source mapping. |
Use <LightboxProvider> when you want component-only composition. Use provideLightbox() instead when your component needs the returned methods directly.
<LightboxRoot>
The dialog shell. It consumes the nearest lightbox context, teleports to <body>, renders only while open, owns focus trapping, and renders the internal FLIP transition layer.
Give custom roots an accessible name with aria-label or aria-labelledby. The included lightbox uses aria-label="Photo viewer".
<LightboxRoot class="fixed inset-0" role="dialog" aria-modal="true" aria-label="Photo viewer">
<!-- overlay, viewport, controls, caption -->
</LightboxRoot><LightboxOverlay>
The semi-transparent backdrop. Clicking the overlay itself closes the lightbox; clicks from slotted children are ignored so controls and custom content do not accidentally close it.
<LightboxOverlay class="my-overlay">
<!-- viewport, controls, etc. -->
</LightboxOverlay>The primitive registers itself with the shared motion controller, so the backdrop follows open, close, and swipe-to-dismiss progress automatically.
<LightboxViewport>
The gesture area. Receives pointer/touch/wheel events and drives the Embla carousel underneath. Exposes a viewport-ref you wire to Embla's container element.
<LightboxViewport v-slot="{ photos, viewportRef, imageLoadFailed }">
<div :ref="viewportRef" class="embla">
<div class="embla__container">
<LightboxSlide
v-for="(photo, i) in photos"
:key="photo.id"
:photo="photo"
:index="i"
/>
</div>
</div>
<div v-if="imageLoadFailed">Image could not be loaded.</div>
</LightboxViewport>Slot props:
photos- the photos list.viewportRef- ref you attach to the Embla container.imageLoadFailed- true when the active slide could not be loaded.
<LightboxSlide>
Renders one slide. Handles zoom state, pan bounds, and the FLIP frame.
<LightboxSlide :photo="photo" :index="i" />Props:
| Prop | Type | Description |
|---|---|---|
photo | PhotoItem | The slide's photo. |
index | number | Index within the photo list. |
effectClass | string | Classes for the effect wrapper. |
frameClass | string | Classes for the frame element. |
zoomClass | string | Classes for the pan/zoom wrapper. |
imgClass | string | Classes on the inner <img>. |
If the context has a custom slide renderer (via provideLightbox's resolveSlide option or a <Photo> parent's #slide slot), <LightboxSlide> delegates to it.
<LightboxControls>
Render any UI you want - close button, prev/next arrows, zoom toggle, counter, share button. The primitive wires up the state and hands it to the default slot.
<LightboxControls
v-slot="{ activeIndex, count, close, next, prev, toggleZoom, isZoomedIn, zoomAllowed }"
>
<button @click="close" aria-label="Close">×</button>
<div>{{ activeIndex + 1 }} / {{ count }}</div>
<button @click="prev" aria-label="Previous">←</button>
<button @click="next" aria-label="Next">→</button>
<button v-if="zoomAllowed" @click="toggleZoom">
{{ isZoomedIn ? 'Zoom out' : 'Zoom in' }}
</button>
</LightboxControls>The primitive also renders a live region announcing Photo 3 of 12 for screen readers.
<LightboxCaption>
Renders caption/description text for the active photo. Default slot gives you photo and activeIndex so you can render whatever the photo's metadata supports.
<LightboxCaption v-slot="{ photo }">
<h2 v-if="photo?.caption">{{ photo.caption }}</h2>
<p v-if="photo?.description">{{ photo.description }}</p>
</LightboxCaption><PhotoTrigger>
PhotoTrigger connects custom thumbnail markup to the nearest provider.
| Prop | Type | Description |
|---|---|---|
photo | PhotoItem | Photo represented by the trigger. |
index | number | Position in the provider collection. |
The default slot receives { photo, index, hidden }. Hide thumbnail overlays when hidden is true so they do not sit above the FLIP transition frame. Click, Enter, and Space open the matching photo.
<PhotoTrigger
v-for="(photo, index) in photos"
:key="photo.id"
:photo="photo"
:index="index"
v-slot="{ hidden }"
>
<figure :style="{ opacity: hidden ? 0 : 1 }">
<PhotoImage :photo="photo" context="thumb" />
</figure>
</PhotoTrigger><PhotoImage>
PhotoImage resolves a source through the current image adapter and renders the final <img>.
| Prop | Type | Default | Description |
|---|---|---|---|
photo | PhotoItem | Required | Photo to render. |
context | 'thumb' | 'slide' | 'thumb' | Delivery context passed to the adapter. |
imageAdapter | ImageAdapter | Provided adapter | Per-instance override. |
loading | 'lazy' | 'eager' | 'lazy' | Native image loading behavior. |
sizes | string | Adapter result | Per-instance sizes override. |
Use it in custom thumbnails and slides when provider-backed image delivery should remain active.