Solving Product Galleries with Pure CSS: Carousel Without JavaScript
AI generated
{ }
@
CSS · Product Gallery · Scroll Snap · E-Commerce
Solving Product Galleries with Pure CSS
Carousel, thumbnails and zoom without JavaScript

Solving a product gallery with pure CSS sounds like a trick, but it is a resilient pattern: scroll snap containers, the checkbox hack principle and the :target pseudo class cover thumbnail navigation, carousel and full screen view without loading a single kilobyte of JavaScript.

17 min read Scroll Snap · Checkbox Hack · :target Magento 2 · Hyva · Product Pages

1. Why a product gallery can work without JavaScript

Most Magento shops ship a JavaScript library for the product gallery that handles thumbnails, zoom and carousel logic. That costs load time, maintenance effort and extra sources of bugs, even though the browser already brings the tools needed to solve a product gallery with pure CSS through scroll snap, the checkbox hack principle and the :target pseudo class. The result is not a workaround, in many cases it is a more robust, faster user experience, because the browser handles the interaction natively instead of through an event listener.

The idea behind a product gallery with pure CSS is to map interaction onto native HTML states: hidden radio buttons control which image is visible, scroll snap provides smooth settling while swiping, and anchor links with :target enable direct jumps to a specific image. The sections below build this gallery step by step, from the basic structure to the full screen view, always with real product pages in Magento in mind.

2. Scroll snap as the foundation of the gallery

Scroll snap is the core of any modern product gallery with pure CSS, because it natively supports horizontal swiping on mobile devices without intercepting touch events. The container gets overflow-x: auto and scroll-snap-type: x mandatory, each image inside gets scroll-snap-align: center. The browser automatically snaps to the next image while scrolling, exactly as users expect from native apps, with zero lines of JavaScript.

An important detail for a product gallery with pure CSS: scroll-behavior: smooth together with scroll-snap-stop: always prevents the user from skipping several images at once during a fast swipe. That matters especially when product images show different views such as close up detail shots that should each be viewed individually instead of flying past the container.


/* Scroll-snap gallery container — no JavaScript needed for swipe */
.gallery-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  gap: 0;
  scrollbar-width: none; /* hide scrollbar, thumbnails handle navigation */
}

.gallery-track::-webkit-scrollbar {
  display: none;
}

.gallery-track > .gallery-slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
}

.gallery-slide img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: contain;
}

3. Thumbnail navigation with the checkbox hack

Clickable thumbnails that swap the main image are the second pillar of any product gallery with pure CSS. The checkbox hack uses hidden input type="radio" elements whose state is read through the :checked pseudo class and the general sibling combinator ~. Each thumbnail is a label pointing to a radio input, each main image reacts through CSS to which radio input is currently selected.

The decisive advantage of this pattern in a product gallery with pure CSS: radio buttons are natively focusable and navigable with arrow keys when grouped in a fieldset. That delivers keyboard accessibility essentially for free, without writing custom keydown handlers as most JavaScript galleries require.


<!-- Checkbox hack: hidden radios drive which image is visible -->
<div class="gallery">
  <input type="radio" name="gallery-img" id="img-1" class="gallery-radio" checked>
  <input type="radio" name="gallery-img" id="img-2" class="gallery-radio">
  <input type="radio" name="gallery-img" id="img-3" class="gallery-radio">

  <div class="gallery-stage">
    <img src="product-1.webp" alt="Product front view" class="gallery-image" data-slot="1">
    <img src="product-2.webp" alt="Product side view" class="gallery-image" data-slot="2">
    <img src="product-3.webp" alt="Product detail view" class="gallery-image" data-slot="3">
  </div>

  <div class="gallery-thumbs">
    <label for="img-1" class="gallery-thumb"><img src="product-1-thumb.webp" alt=""></label>
    <label for="img-2" class="gallery-thumb"><img src="product-2-thumb.webp" alt=""></label>
    <label for="img-3" class="gallery-thumb"><img src="product-3-thumb.webp" alt=""></label>
  </div>
</div>

4. Direct jumps with the :target pseudo class

Besides the checkbox hack, the :target pseudo class offers an alternative approach for a product gallery with pure CSS, especially when images should also be linkable through direct URL anchors, for example from a product description pointing at a specific detail image. Each gallery image gets its own id, links with href="#image-2" set the URL hash, and :target matches the element whose id currently sits in the hash.

The difference to the checkbox hack: :target reacts to the URL hash and therefore stays active after a page reload or when a link is shared directly through the hash anchor. For a product gallery with pure CSS that needs to support deep links to individual product images, this is often the better choice than plain radio buttons, which always fall back to the default state after a reload.


/* :target based gallery — supports deep links like #view-detail */
.gallery-panel {
  display: none;
}

.gallery-panel:target,
.gallery-panel:first-of-type:not(:target ~ .gallery-panel) {
  display: block;
}

/* Highlight the active thumbnail link when its target is active */
.gallery-thumb-link:has(+ #view-detail:target) {
  outline: 2px solid var(--color-brand-600, #7c3aed);
}

5. Progress indicators without counting in JavaScript

A visible progress indicator, for example dots below the carousel, is part of the expected interaction logic for many users of a product gallery with pure CSS. Since the current state is already known through :checked or :target, the indicator dots can simply be coupled to that same state through sibling selectors. An active dot gets a different background color as soon as its associated radio input is active, entirely without counting anything in JavaScript.

With many images, however, the pure CSS selector chain becomes hard to read, because each image needs its own selector with the matching sibling combinator. In practice this works well up to about eight to ten images, which is enough for most e-commerce product galleries. Beyond that, switching to a server generated stylesheet that produces selectors automatically for the actual image count pays off.

Feature CSS Only Approach Typical JS Slider Recommendation
Thumbnail switching Checkbox hack, instantly interactive Event listener per thumbnail CSS only up to 10 images
Touch swiping Scroll snap native Must compute touch events itself CSS only almost always better
Deep links to images :target native via hash Custom router logic needed CSS only simpler
Autoplay / timer Only approximated with CSS animations Fully controllable JS when autoplay is needed
Zoom on click :target overlay pattern Lightbox library required CSS only saves load time

6. Full screen view as a pure CSS overlay

A full screen view when clicking the main image can be implemented in a product gallery with pure CSS using the same :target pattern used for deep links. A link with href="#zoom-1" activates an overlay element that uses position: fixed to cover the entire viewport and shows the image at full resolution. A second link with href="#" inside the overlay resets the hash and closes the view again.

A calm user experience comes from a transition on opacity and visibility, combined with a short delay via transition-delay when closing, so the overlay only becomes invisible once the fade out animation has completed. This technique makes the product gallery with pure CSS complete, because even the zoom feature, which in many shops requires its own lightbox library, works without any additional JavaScript code.

7. Accessibility: keyboard and screen readers

Accessibility is not a side note for a product gallery with pure CSS, it is often even an advantage over JavaScript solutions, because native form elements like radio buttons are automatically announced correctly by screen readers as a group with a current selection state. It matters that the radio inputs are not hidden with display: none, because that removes them from screen readers too, but with a visually hidden class that keeps the elements in the accessibility tree.

In addition, every thumbnail label should describe through an aria-label which image it activates, for example "Show product view 2 of 5". For the :target based zoom overlay, a visible, easily reachable close button with sufficient contrast is mandatory, otherwise keyboard users have no obvious way to leave the overlay without a mouse. These details decide whether a product gallery with pure CSS actually works for all users or just looks convincing.


/* Visually hide radio inputs, keep them in the accessibility tree */
.gallery-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Visible focus ring on the associated thumbnail label */
.gallery-radio:focus-visible + .gallery-thumb {
  outline: 3px solid var(--color-brand-600, #7c3aed);
  outline-offset: 2px;
}

8. Limits of the CSS only product gallery

As capable as a product gallery with pure CSS is, it has clear limits. An automatic image change on a fixed interval can be approximated through CSS animations with animation-timeline, but a pausable, user controllable autoplay timer requires real JavaScript code, because CSS has no access to user interaction outside of hover, focus and click states.

Dynamically loaded images, for example a product gallery that loads variant images via Ajax, also exceed the pure CSS approach, because new radio inputs and labels would need to be inserted into the DOM at runtime. In such cases a hybrid approach is sensible: the base gallery stays CSS only, and a small Alpine.js snippet only handles loading additional images, without replacing the existing CSS logic.

9. CSS only vs. JavaScript gallery compared

The choice between a product gallery with pure CSS and a classic JavaScript library depends heavily on the functional requirements, but in most standard e-commerce product galleries the CSS approach is fully sufficient, as the table in section five showed. The decisive advantage remains load time: no additional JavaScript file, no parsing overhead, no layout shift caused by later hydration.

Especially on category pages with many product cards, each showing its own mini gallery on hover, the difference becomes clearly noticeable. A hundred JavaScript initialized galleries on a category page cost noticeable compute time on first render, while a hundred CSS only galleries barely add any extra load to the browser, because the logic is already part of normal style computation.

Mironsoft

Fast product pages without unnecessary JavaScript

Product gallery without JavaScript overhead?

We build product galleries with scroll snap, checkbox hack and :target patterns that run smoothly on every device and need no extra slider library.

Gallery Audit

Checking existing JS sliders for a CSS only replacement

Implementation

Building thumbnails, zoom and carousel purely with CSS

Accessibility

Ensuring keyboard and screen reader accessibility

10. Summary

Solving a product gallery with pure CSS is not a compromise, in most standard cases it is the faster and more robust alternative to a JavaScript library. Scroll snap handles swipe behavior on touch devices, the checkbox hack controls thumbnail selection, and :target enables both deep links to individual images and a full screen overlay for the zoom feature.

The limits sit at autoplay timers and dynamically loaded images, where a hybrid approach with a small Alpine.js addition makes more sense than a purely CSS based construct. For the vast majority of product pages in a Magento environment, though, the product gallery with pure CSS is fully sufficient and measurably saves load time compared to any external slider library.

Product Gallery with Pure CSS — The Essentials at a Glance

Scroll Snap

scroll-snap-type: x mandatory handles touch swiping natively, without event listeners.

Checkbox Hack

Hidden radio inputs and :checked control thumbnail selection, keyboard friendly through fieldset.

:target Deep Links

URL hash controls the visible image, survives reloads and works for shared links.

Limits

Autoplay and dynamically loaded images need a hybrid approach with Alpine.js.

11. FAQ: Product Gallery with Pure CSS

1Is a CSS only product gallery production ready?
Yes, for thumbnails, swiping, zoom and deep links CSS is fully sufficient.
2How does the checkbox hack work?
Hidden radio inputs store state, labels are clickable thumbnails, :checked controls the main image.
3What does scroll snap do?
Settles automatically on the next image while swiping, native and without touch events.
4What is :target useful for?
Deep links and a full screen overlay that shows the right state after a reload.
5Is the gallery accessible?
Often better than JS when done right, as long as radios are not hidden with display none.
6How many images are reasonable?
Works well up to eight to ten images, beyond that a server generated stylesheet pays off.
7Can it autoplay without JavaScript?
Only limited via animations, a controllable timer needs real JavaScript.
8Does it work with lazy loaded images?
Only limited, a hybrid approach with Alpine.js for loading is more practical.
9How is the zoom overlay built?
A link activates a fixed overlay via :target, a second link resets the hash.
10Is switching from JS worth it?
Yes for standard needs, especially on category pages with many mini galleries.