Skip to main content

Customize the built-in lightbox

Change captions, actions, counters, or slide markup without rebuilding the lightbox from primitives.

Use this guide when you like Nuxt Photo's built-in lightbox behavior, but want different UI.

Use this focused customization path for:

  • custom captions
  • different top-bar actions
  • a custom counter
  • slide markup that still lives inside the built-in lightbox

If you need a different component structure, use the primitives guide.

1. Wrap the included lightbox

Create a component that renders <Lightbox> and overrides only the slots you need.

components/MyLightbox.vue
<script setup lang="ts">
import { Lightbox } from '@lupinum/nuxt-photo/app'
</script>

<template>
  <Lightbox>
    <template #counter="{ activeIndex, count }">
      <div class="text-sm opacity-70">{{ activeIndex + 1 }} of {{ count }}</div>
    </template>

    <template #caption="{ photo, index }">
      <div class="space-y-1">
        <div class="text-xs uppercase tracking-wide opacity-60">Frame {{ index + 1 }}</div>
        <h2 v-if="photo?.caption" class="text-lg font-medium">
          {{ photo.caption }}
        </h2>
        <p v-if="photo?.description" class="text-sm opacity-80">
          {{ photo.description }}
        </p>
      </div>
    </template>

    <template
      #actions="{
        activeIndex,
        count,
        prev,
        next,
        close,
        toggleZoom,
        isZoomedIn,
        zoomAllowed,
        controlsDisabled,
      }"
    >
      <button :disabled="controlsDisabled || activeIndex <= 0" @click="prev">Prev</button>
      <button :disabled="controlsDisabled || activeIndex >= count - 1" @click="next">Next</button>
      <button :disabled="controlsDisabled || !zoomAllowed" @click="toggleZoom()">
        {{ isZoomedIn ? 'Fit' : 'Zoom' }}
      </button>
      <button :disabled="controlsDisabled" @click="close">Close</button>
    </template>
  </Lightbox>
</template>

That keeps the built-in gesture handling, transitions, focus behavior, and slide wiring. You only replace the visible pieces.

2. Use it on one component

Pass the wrapper through the lightbox prop on the ready-made component you want to customize.

PhotoAlbum

vue
<script setup lang="ts">
import MyLightbox from '~/components/MyLightbox.vue'
</script>

<template>
  <PhotoAlbum :photos="photos" :lightbox="MyLightbox" />
</template>

PhotoGroup

vue
<script setup lang="ts">
import MyLightbox from '~/components/MyLightbox.vue'
</script>

<template>
  <PhotoGroup :photos="photos" :lightbox="MyLightbox">
    <PhotoAlbum :photos="photos" />
  </PhotoGroup>
</template>

PhotoCarousel

vue
<script setup lang="ts">
import MyLightbox from '~/components/MyLightbox.vue'
</script>

<template>
  <PhotoCarousel :photos="photos" :lightbox="MyLightbox" />
</template>

3. Customize slides only when you need to

The included <Lightbox> also supports a slide slot:

vue
<script setup lang="ts">
import { Lightbox, PhotoImage } from '@lupinum/nuxt-photo/app'
</script>

<template>
  <Lightbox>
    <template #slide="{ photo }">
      <figure class="relative h-full w-full">
        <PhotoImage :photo="photo" context="slide" class="h-full w-full object-contain" />
        <figcaption class="absolute right-4 bottom-4 rounded bg-black/70 px-3 py-2 text-white">
          {{ photo.caption }}
        </figcaption>
      </figure>
    </template>
  </Lightbox>
</template>

Use this when the built-in lightbox is still the right shell, but a photo needs an overlay or different image composition.

When this guide is enough

Stay on this path when:

  • the built-in lightbox behavior is correct
  • you only want different UI chrome
  • you want to keep the existing lightbox behavior

Move to Build a lightbox from primitives when you need to compose the overlay, viewport, controls, and caption yourself.

Apply the lightbox globally

If most galleries in the app should use the same custom lightbox, set it once globally.

app/plugins/photo-lightbox.ts
import { LightboxComponentKey } from '@lupinum/nuxt-photo/app'
import MyLightbox from '~/components/MyLightbox.vue'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.provide(LightboxComponentKey, MyLightbox)
})

Ready-made components now use MyLightbox unless their lightbox prop provides a different component. The global component does not enable a lightbox by itself. Photo and PhotoCarousel still require lightbox to be enabled. An explicit component passed through the lightbox prop takes precedence over the global component.