Fylgja Gallery

A gallery shows a set of images as an even grid of thumbnails. It is a plain list of <img> elements laid out with the auto-grid utility, so it reflows by the available width without media queries, and the lightbox variant opens each image in a native <dialog> with focus handling and a backdrop for free.

Preview

  • An orange fox sitting in dry grass
  • A red deer stag with wide antlers in a dark forest
  • A hare sitting upright at the edge of a wood
  • A falcon in flight over open ground
  • A brown bear looking up
  • A frog resting among lily pads
<ul role="list" class="auto-grid gap" style="--max-col-size: 10rem">
	<li>
		<img
			src="/images/placeholders/fox.webp"
			alt="An orange fox sitting in dry grass"
			width="540"
			height="360"
			loading="lazy"
			class="rounded"
		/>
	</li>
	<li>
		<img
			src="/images/placeholders/deer.webp"
			alt="A red deer stag with wide antlers in a dark forest"
			width="540"
			height="360"
			loading="lazy"
			class="rounded"
		/>
	</li>
	<li>
		<img
			src="/images/placeholders/hare.webp"
			alt="A hare sitting upright at the edge of a wood"
			width="540"
			height="360"
			loading="lazy"
			class="rounded"
		/>
	</li>
	<li>
		<img
			src="/images/placeholders/falcon.webp"
			alt="A falcon in flight over open ground"
			width="540"
			height="360"
			loading="lazy"
			class="rounded"
		/>
	</li>
	<li>
		<img
			src="/images/placeholders/bear.webp"
			alt="A brown bear looking up"
			width="540"
			height="360"
			loading="lazy"
			class="rounded"
		/>
	</li>
	<li>
		<img
			src="/images/placeholders/frog.webp"
			alt="A frog resting among lily pads"
			width="540"
			height="360"
			loading="lazy"
			class="rounded"
		/>
	</li>
</ul>

Usage

  • Wrap the images in a <ul role="list" class="auto-grid gap"> and set the minimum thumbnail width with --max-col-size.
  • Give every <img> its width and height, so the browser reserves the space before it loads, and loading="lazy" so images below the fold wait.
  • When the images differ in size, add an aspect-ratio and object-cover so they crop to the same shape.

Wrap each thumbnail in a button that opens a modal with the larger image and a caption. The --btn-px, --btn-py, and --btn-border-width variables on the list strip the button padding and border, so only the image shows. Every dialog needs its own id, matched by the thumbnail’s commandfor.

An orange fox sitting in dry grass
Red fox, late autumn
A red deer stag with wide antlers in a dark forest
Red deer stag at dusk
A hare sitting upright at the edge of a wood
Brown hare in the morning sun
A falcon in flight over open ground
Peregrine falcon on the hunt
<ul role="list" class="auto-grid gap" style="--max-col-size: 10rem; --btn-px: 0; --btn-py: 0; --btn-border-width: 0">
	<li>
		<button type="button" commandfor="gallery-fox" command="show-modal" class="w-full">
			<img
				src="/images/placeholders/fox.webp"
				alt="An orange fox sitting in dry grass"
				width="540"
				height="360"
				loading="lazy"
				class="rounded"
			/>
		</button>
	</li>
	<li>
		<button type="button" commandfor="gallery-deer" command="show-modal" class="w-full">
			<img
				src="/images/placeholders/deer.webp"
				alt="A red deer stag with wide antlers in a dark forest"
				width="540"
				height="360"
				loading="lazy"
				class="rounded"
			/>
		</button>
	</li>
	<li>
		<button type="button" commandfor="gallery-hare" command="show-modal" class="w-full">
			<img
				src="/images/placeholders/hare.webp"
				alt="A hare sitting upright at the edge of a wood"
				width="540"
				height="360"
				loading="lazy"
				class="rounded"
			/>
		</button>
	</li>
	<li>
		<button type="button" commandfor="gallery-falcon" command="show-modal" class="w-full">
			<img
				src="/images/placeholders/falcon.webp"
				alt="A falcon in flight over open ground"
				width="540"
				height="360"
				loading="lazy"
				class="rounded"
			/>
		</button>
	</li>
</ul>

<dialog id="gallery-fox" closedby="any" aria-labelledby="gallery-fox-caption" class="flow-none">
	<figure>
		<img src="/images/placeholders/fox.webp" alt="An orange fox sitting in dry grass" width="540" height="360" loading="lazy" class="rounded" />
		<figcaption class="flex align gap" style="--py: 1rem 0">
			<span id="gallery-fox-caption">Red fox, late autumn</span>
			<button type="button" commandfor="gallery-fox" command="close" aria-label="Close">
				<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
					<path d="M6 6L18 18M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
				</svg>
			</button>
		</figcaption>
	</figure>
</dialog>

<dialog id="gallery-deer" closedby="any" aria-labelledby="gallery-deer-caption" class="flow-none">
	<figure>
		<img src="/images/placeholders/deer.webp" alt="A red deer stag with wide antlers in a dark forest" width="540" height="360" loading="lazy" class="rounded" />
		<figcaption class="flex align gap" style="--py: 1rem 0">
			<span id="gallery-deer-caption">Red deer stag at dusk</span>
			<button type="button" commandfor="gallery-deer" command="close" aria-label="Close">
				<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
					<path d="M6 6L18 18M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
				</svg>
			</button>
		</figcaption>
	</figure>
</dialog>

<dialog id="gallery-hare" closedby="any" aria-labelledby="gallery-hare-caption" class="flow-none">
	<figure>
		<img src="/images/placeholders/hare.webp" alt="A hare sitting upright at the edge of a wood" width="540" height="360" loading="lazy" class="rounded" />
		<figcaption class="flex align gap" style="--py: 1rem 0">
			<span id="gallery-hare-caption">Brown hare in the morning sun</span>
			<button type="button" commandfor="gallery-hare" command="close" aria-label="Close">
				<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
					<path d="M6 6L18 18M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
				</svg>
			</button>
		</figcaption>
	</figure>
</dialog>

<dialog id="gallery-falcon" closedby="any" aria-labelledby="gallery-falcon-caption" class="flow-none">
	<figure>
		<img src="/images/placeholders/falcon.webp" alt="A falcon in flight over open ground" width="540" height="360" loading="lazy" class="rounded" />
		<figcaption class="flex align gap" style="--py: 1rem 0">
			<span id="gallery-falcon-caption">Peregrine falcon on the hunt</span>
			<button type="button" commandfor="gallery-falcon" command="close" aria-label="Close">
				<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
					<path d="M6 6L18 18M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
				</svg>
			</button>
		</figcaption>
	</figure>
</dialog>

Anatomy

Part Responsibility
<ul class="auto-grid gap"> The grid, reflowing by available width.
<img> A thumbnail, with its own alt, width, and height.
<button command="show-modal"> Lightbox only. Opens the dialog for its image.
<dialog> with <figure> Lightbox only. The large image and its caption.

Accessibility

  • Write an alt that describes each image. In the lightbox, the thumbnail’s alt also names its button, so it tells the reader what opens.
  • Label each dialog with aria-labelledby pointing at its caption.
  • The dialog moves focus in when it opens and returns it to the thumbnail when it closes. Escape closes it, and so does a click on the backdrop.
  • Give the icon only close button an aria-label.

Related: Bento Grid, Modal, Carousel.

FAQ

How do I change the number of columns?

auto-grid fits as many columns as the width allows. Lower the --max-col-size variable for more, smaller thumbnails, or raise it for fewer, larger ones.

My images have different sizes. How do I keep the grid even?

Give every image the same aspect-ratio, such as 1 for squares, and the object-cover utility, so each one is cropped to fill its cell instead of stretching.

Does the lightbox need JavaScript?

No. Each thumbnail is a button with invoker commands that opens its own dialog, and closedby lets Escape or a click on the backdrop close it again.

Can the lightbox step to the next image?

Not with one dialog per image. Previous and next buttons need a script that swaps the image in a single dialog, or a Carousel inside the dialog.

Should the thumbnail load the full size image?

Ideally not. Point the thumbnail at a small version and the dialog at the large one. The dialog image only loads once it is shown when you add loading lazy to it.