Skip to main content

Lightbox

Lightbox

Full-screen media viewer with zoom, pan, and keyboard navigation

Tags: component overlay media

View Demo

Lightbox presents media full-screen over a dimmed backdrop, paging through a list with the keyboard, arrows, or a swipe. Images can be zoomed and panned; video and audio play with the standard themed controls.

It is built directly on the dialog primitive, so focus trapping, Escape to close, scroll locking, and aria-modal all behave the same way as Dialog.

Import​

<script>
  import { Lightbox } from '@classic-homes/theme-svelte';
</script>

Basic Usage​

<script>
  let open = $state(false);
  let index = $state(0);

  const photos = [
    { id: '1', kind: 'image', src: '/one.jpg', alt: 'Front elevation', caption: 'Front elevation' },
    { id: '2', kind: 'image', src: '/two.jpg', alt: 'Kitchen' },
  ];
</script>

<Button onclick={() => (open = true)}>View photos</Button>

<Lightbox items={photos} bind:open bind:index />

Props​

Prop Type Default Description
items MediaItem[] required Items to page through
open boolean false Whether the viewer is open (bindable)
index number 0 Index of the visible item (bindable)
loop boolean true Wrap from the last item back to the first
showThumbnails boolean false Show the thumbnail strip along the bottom
showCaption boolean true Show the caption beneath the media
showCounter boolean true Show the "n of m" counter
zoomable boolean true Allow zooming and panning images
downloadable boolean false Show a download link for items that expose a URL
onOpenChange (open: boolean) => void - Called when the open state changes
onIndexChange (index: number) => void - Called when the visible item changes
class string - Additional CSS classes for the surface
trigger Snippet<[{ props }]> - Optional trigger; spread props on your own button. Omit when controlling open yourself

Snippets​

Snippet Description
trigger Element that opens the viewer; receives { props } to spread on your button. Omit if controlling open

Keyboard Shortcuts​

Key Action
Escape Close
← / → Previous/next item, or pan when zoomed
↑ / ↓ Pan when zoomed
Home / End First/last item
+ / - Zoom in/out
0 Reset zoom

Zoom and Pan​

Images support wheel zoom, pinch zoom, double-click or double-tap to toggle, and drag to pan. Zoom resets whenever the item changes, so a magnified view never carries onto the next photo. Swipe navigation is only active while the image is at its natural size, so panning and paging never conflict.

With a Trigger​

Use the trigger snippet for an inline trigger button. It receives { props }: spread them on your own button so it becomes the trigger, carries aria-expanded, and gets focus back when the viewer closes. A snippet that ignores them still works, wrapped in a span as before.

<Lightbox items={photos}>
  {#snippet trigger({ props })}
    <Button {...props} variant="outline">Open gallery</Button>
  {/snippet}
</Lightbox>

Mixed Media​

One list can hold images, video, audio, and provider embeds together:

<script>
  const items = [
    { id: '1', kind: 'image', src: '/photo.jpg', alt: 'Exterior' },
    { id: '2', kind: 'video', src: '/tour.mp4', poster: '/tour.jpg', title: 'Walkthrough' },
    { id: '3', kind: 'embed', provider: 'youtube', videoId: 'dQw4w9WgXcQ', title: 'Overview' },
  ];
</script>

<Lightbox {items} bind:open showThumbnails />

Moving to another item unmounts the outgoing player, so a video or track never keeps playing out of sight.

Accessibility​

  • A modal dialog: focus is trapped inside, Escape closes, and the page behind is locked from scrolling.
  • When opened from the trigger, focus returns to that button on close.
  • Named from the active item's title or alt text, and described by its caption.
  • Navigation, zoom, download, and close controls are all labelled and sized to a comfortable touch target.
  • The neighbouring images are preloaded, so paging does not flash an empty frame.
  • Transitions are skipped entirely under prefers-reduced-motion.
On this page