Vue Lightbox
Full-screen image & video gallery lightbox for Vue 3, built on @reelkit/vue-lightbox.
Features
Installation
Don't forget to import the styles:
Icons
lucide-vue-next for icons. If you prefer a different icon library, use the #controls and #navigation scoped slots to provide your own.Basic Usage
Import the stylesheet and the LightboxOverlay component, then drive open/close with v-model:is-open.
Scoped Slots
Six named scoped slots allow full customisation of the overlay surfaces. Omit the slot to keep the built-in default; provide nothing inside the slot (e.g. via v-if="false") to hide that section entirely.
| Slot | Scope | Description |
|---|---|---|
| #slide | SlideSlotScope | Replace individual slide content (required for video slides) |
| #controls | ControlsSlotScope | Replace the top controls bar (close, counter, fullscreen) |
| #navigation | NavigationSlotScope | Replace the prev/next navigation arrows |
| #info | InfoSlotScope | Replace the bottom title/description gradient overlay |
| #loading | LoadingSlotScope | Custom loading indicator |
| #error | ErrorSlotScope | Custom error indicator |
Video Support
Video slides are opt-in so the default bundle stays free of audio/video wiring. Call useVideoSlideRenderer(items) and forward the returned VideoSlideRenderer / VideoControlsRenderer into the overlay's #slide and #controls slots. Wrap the overlay in the returned SoundProvider so the built-in sound toggle has a context.
<video> element powering video slides uses the same pattern as the vue reel-player — playback continues across slide changes on iOS without requiring a per-slide user gesture.Fullscreen
Use useFullscreen from @reelkit/vue to observe or toggle fullscreen state on a referenced element. The lightbox drives its built-in fullscreen button through the same composable.
LightboxOverlay Props
| Prop | Type | Default | Description |
|---|---|---|---|
| isOpen | boolean | required | Controls visibility; when false the overlay is removed from the DOM. Bindable via v-model:is-open. |
| items | LightboxItem[] | required | Array of items (images or videos) |
| initialIndex | number | 0 | Zero-based index of the initially visible item |
| transitionFn | TransitionTransformFn | slideTransition | Slide transition function. Import a built-in (slideTransition, flipTransition, lightboxFadeTransition, lightboxZoomTransition) or pass a custom one. Defaults to slideTransition when omitted. |
| showInfo | boolean | true | Whether to render the title/description info overlay |
| showControls | boolean | true | Whether to render the top controls bar (close, counter, fullscreen) |
| showNavigation | boolean | true | Whether to render the prev/next navigation arrows (desktop only) |
| transitionDuration | number | 300 | Slide animation duration in ms |
| swipeDistanceFactor | number | 0.12 | Minimum swipe distance fraction (0–1) to trigger slide change |
| swipeToCloseDirection | 'up' | 'down' | 'up' | Direction of the swipe-to-close gesture on mobile |
| loop | boolean | false | Whether the slider wraps from the last slide back to the first |
| enableNavKeys | boolean | true | Enable keyboard arrow-key navigation |
| enableWheel | boolean | true | Enable mouse-wheel navigation |
| wheelDebounceMs | number | 200 | Debounce duration for wheel events in ms |
| ariaLabel | string | 'Image gallery' | Accessible label for the dialog region |
LightboxOverlay Events
| Event | Payload | Description |
|---|---|---|
| close | void | Emitted when the user closes the lightbox |
| slide-change | number | Emitted with the new active slide index after a change |
| api-ready | LightboxApi | Emitted once the slider is ready, exposing the imperative API |
| update:is-open | boolean | Emitted on close; enables v-model:is-open |
LightboxItem Interface
| Field | Type | Required | Description |
|---|---|---|---|
| src | string | yes | URL of the image or video |
| type | 'image' | 'video' | no | Item type. Defaults to 'image' |
| poster | string | no | Thumbnail image for video items |
| title | string | no | Title shown in the info overlay |
| description | string | no | Description shown below the title |
| width | number | no | Intrinsic image width in pixels |
| height | number | no | Intrinsic image height in pixels |
Slot Scope Types
| Type | Fields |
|---|---|
| SlideSlotScope | { item, index, size: [number, number], isActive, onReady, onWaiting, onError } |
| ControlsSlotScope | { item, activeIndex, count, isFullscreen, onClose, onToggleFullscreen } |
| NavigationSlotScope | { item, activeIndex, count, onPrev, onNext } |
| InfoSlotScope | { item, index } |
| LoadingSlotScope | { item, activeIndex } |
| ErrorSlotScope | { item, activeIndex } |
Transitions
Pass any TransitionTransformFn via the transition-fn prop. Importing only the transition you use lets the bundler tree-shake the rest. Defaults to slideTransition when omitted.
| Function | Description |
|---|---|
| slideTransition | Default. Horizontal translate between slides; re-exported from @reelkit/vue. |
| lightboxFadeTransition | Crossfade with a subtle horizontal nudge. Local to @reelkit/vue-lightbox. |
| flipTransition | 3D flip around the Y-axis; re-exported from @reelkit/vue. |
| lightboxZoomTransition | Incoming slide scales 70% → 100% with fade. Local to @reelkit/vue-lightbox. |
Content Loading & Error Handling
When you take over rendering via the #slide slot, three lifecycle callbacks are available on the slot scope to report loading state. The lightbox tracks per-slide state and shows a spinner or error icon accordingly. A content preloader caches broken URLs so revisiting a failed slide skips the retry.
Lifecycle callbacks
| Callback | Type | Description |
|---|---|---|
| onReady | () => void | Notify that the slide content has loaded successfully (e.g. image decoded) |
| onWaiting | () => void | Notify that the slide content is loading/buffering (shows spinner) |
| onError | () => void | Notify that the slide content failed to load (shows error icon) |
Wiring callbacks in #slide
Custom loading slot
Use the #loading slot to replace the default spinner.
Custom error slot
Use the #error slot to replace the default broken-image icon.
CSS Classes
All CSS classes are plain (not scoped), so they can be targeted with higher-specificity selectors in a stylesheet loaded after @reelkit/vue-lightbox/styles.css. For color, size, and z-index changes, prefer the CSS custom properties documented in the Theming section below.
| Class | Component | Description |
|---|---|---|
| .rk-lightbox-overlay | Overlay | Root container (full-screen backdrop) |
| .rk-lightbox-top-shade | Overlay | Top gradient scrim behind controls |
| .rk-lightbox-spinner | Overlay | Default loading spinner |
| .rk-lightbox-error | Overlay | Error state container (broken image) |
| .rk-lightbox-error-text | Overlay | Error state text label |
| .rk-lightbox-controls-left | Controls | Top-left controls container |
| .rk-lightbox-btn | Controls | Control button (fullscreen, sound, etc.) |
| .rk-lightbox-close | Controls | Close button |
| .rk-lightbox-counter | Controls | Image counter chip |
| .rk-lightbox-nav | Navigation | Navigation arrow (both prev and next) |
| .rk-lightbox-nav-prev | Navigation | Previous arrow |
| .rk-lightbox-nav-next | Navigation | Next arrow |
| .rk-lightbox-info | Info | Title / description container |
| .rk-lightbox-info-title | Info | Image title |
| .rk-lightbox-info-description | Info | Image description |
| .rk-lightbox-slide | Slide | Slide container |
| .rk-lightbox-img | Slide | Image element |
| .rk-lightbox-video-container | VideoSlide | Video slide container (opt-in) |
| .rk-lightbox-video-element | VideoSlide | Video element (opt-in) |
| .rk-lightbox-video-poster | VideoSlide | Video poster image (opt-in) |
Theming
Override any --rk-lightbox-* CSS custom property on :root (or any ancestor of .rk-lightbox-overlay) to retheme. Direct declarations on .rk-lightbox-overlay would shadow inherited values, so keep overrides on an ancestor selector.
| Token | Default | Controls |
|---|---|---|
| --rk-lightbox-overlay-bg | #000 | Backdrop color |
| --rk-lightbox-overlay-z | 9999 | Overlay z-index |
| --rk-lightbox-top-shade-height | 80px | Top scrim height |
| --rk-lightbox-top-shade-bg | linear-gradient(rgba(0,0,0,0.6), transparent) | Top scrim gradient |
| --rk-lightbox-edge-padding | 16px | Edge inset for close / nav / controls |
| --rk-lightbox-btn-bg | rgba(0, 0, 0, 0.5) | Default background for close / nav / small buttons |
| --rk-lightbox-btn-bg-hover | rgba(255, 255, 255, 0.2) | Hover background for close / nav / small buttons |
| --rk-lightbox-btn-fg | #fff | Icon color for close / nav / small buttons |
| --rk-lightbox-btn-size | 36px | Small button size (fullscreen toggle, etc.) |
| --rk-lightbox-close-size | 40px | Close button size |
| --rk-lightbox-nav-size | 48px | Prev / next arrow size |
| --rk-lightbox-nav-opacity | 0.7 | Idle opacity of prev / next arrows |
| --rk-lightbox-counter-bg | rgba(0, 0, 0, 0.5) | Counter chip background |
| --rk-lightbox-counter-fg | #fff | Counter text color |
| --rk-lightbox-info-bg | linear-gradient(transparent, rgba(0,0,0,0.8)) | Caption scrim gradient |
| --rk-lightbox-title-size | 18px | Title font size |
| --rk-lightbox-description-size | 14px | Description font size |
| --rk-lightbox-video-bg | #000 | Letterbox background behind <video> |
Accessibility
The overlay root is a modal dialog (role="dialog", aria-modal="true"). Set the aria-label prop to change the screen-reader announcement; it defaults to "Image gallery". Each slide carries role="group", aria-roledescription="slide", and an aria-label derived from the position (e.g. "Image 2 of 5").
The lightbox captures focus on open and returns it to the trigger on close. Tab and Shift+Tab cycle through focusable elements inside; focus that escapes (click outside, programmatic focus) gets pulled back. Implemented with captureFocusForReturn and createFocusTrap from @reelkit/vue.
Keyboard Shortcuts
| Key | Action |
|---|---|
| ArrowLeft | Previous image |
| ArrowRight | Next image |
| Escape | Close lightbox (or exit fullscreen if active) |