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.
Table of Contents
- 1. Why a product gallery can work without JavaScript
- 2. Scroll snap as the foundation of the gallery
- 3. Thumbnail navigation with the checkbox hack
- 4. Direct jumps with the :target pseudo class
- 5. Progress indicators without counting in JavaScript
- 6. Full screen view as a pure CSS overlay
- 7. Accessibility: keyboard and screen readers
- 8. Limits of the CSS only product gallery
- 9. CSS only vs. JavaScript gallery compared
- 10. Summary
- 11. FAQ
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.