Photo galleries with zero JavaScript

A gallery component with a full lightbox — expand, close, prev/next — shipping not a single byte of JS.

This site now has a <Gallery> component, and it cheats in the best way: the lightbox — click to expand, click outside or press Esc to close, arrows to move between photos — is handled almost entirely by the browser’s native Popover API. Total JavaScript shipped: one ~200-byte snippet.

One photo

A single photo renders full width and keeps its natural aspect ratio. Click it:

Several photos

Multiple photos snap into a grid — click any of them, then use the arrows (or Tab + Enter) to flip through:

How it works

Each thumbnail is a <button popovertarget="photo-3">, and each full-size photo is a <div popover>. The browser does the rest: open on click, light dismiss, focus handling. The prev/next arrows are just buttons targeting the neighboring photo’s popover. The fade-in is CSS @starting-style, and the images themselves are resized and converted to modern formats at build time by Astro.

The one place the browser needs help: when you open photo 2 from inside photo 1, the Popover API considers photo 1 an ancestor and stacks the two instead of swapping them. A tiny script listens for beforetoggle and closes the previous photo, adds / navigation, and turns horizontal swipes into prev/next on touch screens — that’s the entire JavaScript budget of this component.

import Gallery from '../../components/Gallery.astro';
import dusk from '../../assets/photos/dusk.jpg';

<Gallery photos={[{ src: dusk, alt: 'Warm gradient at dusk', caption: 'Dusk.' }]} />