Thumbnail gallery

A lazy-loaded gallery linking to larger images

Introduction

A gallery of thumbnails is one of the most common patterns on the web, and also one of the easiest to get wrong from a performance perspective. By default, the browser starts fetching every <img> it parses — even images that are hundreds of pixels below the fold and may never be seen. On a gallery page with dozens of thumbnails that can mean dozens of unnecessary network requests on initial load.

This example applies the lazy-loading technique from Tutorial 09: Performance & Loading to a three-image gallery. Each thumbnail uses loading="lazy" so the browser defers its fetch until the image approaches the viewport. Each thumbnail also links to the full-resolution version using a plain <a>, so the larger image is accessible without any JavaScript.

Live demo

Three thumbnails — a product shot, a portrait, and a landscape — are presented in a responsive grid. Every thumbnail has loading="lazy", decoding="async", and explicit width and height attributes. Click any thumbnail to open its full-resolution JPEG in the browser.

  • Close-up of a ceramic coffee mug on a wooden table
    Product — 400×266
  • Studio portrait of a young woman with wavy brown hair and red lips against a dark grey background
    Portrait — 400×600
  • Winding single-track road through misty green Scottish highland cliffs under an overcast sky
    Landscape — 400×266

Key markup

Below is the markup for a single gallery item. The pattern is the same for all three: an <a> wraps the thumbnail and points to the full-resolution source; the <img> inside carries the lazy-loading attributes and its declared intrinsic size.

<figure> <a href="../assets/product-1200.jpg"> <img src="../assets/product-400.jpg" alt="Close-up of a ceramic coffee mug on a wooden table" width="400" height="266" loading="lazy" decoding="async" > </a> <figcaption>Product — 400×266</figcaption> </figure>

Why these choices matter

loading="lazy" saves bandwidth up front

When a user arrives at the page, only images near the viewport need to be fetched immediately. loading="lazy" tells the browser to skip the network request until the image is within a browser-determined threshold of the viewport (typically a few hundred pixels). On a gallery page with many images, this can eliminate the majority of image fetches on initial load, freeing bandwidth for the content the user is actually looking at.

width and height prevent layout shift

When the browser defers an image fetch it also defers knowing how tall the image will be. Without width and height attributes, the reserved height is zero; when the image eventually loads it pushes surrounding content down, causing a jarring Cumulative Layout Shift (CLS). Providing the intrinsic dimensions lets the browser calculate the correct aspect ratio and reserve the right amount of space even before the image arrives.

decoding="async" keeps the main thread free

Decompressing a JPEG or WebP file takes CPU time. By default, the browser may perform this work synchronously, blocking style calculations and rendering while it finishes. decoding="async" moves the decode off the main thread so the rest of the page can continue rendering. For thumbnails — which are non-critical by definition — this is always appropriate.

Plain links instead of a lightbox

A JavaScript lightbox provides a polished UX, but it adds complexity, a script dependency, and potential accessibility burden. For a learning context, wrapping the thumbnail in a plain <a href="full-image.jpg"> is simpler, works without JavaScript, and illustrates the core concept (thumbnail → full resolution) clearly. Real projects can layer a lightbox on top of this foundation.