SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/image-sliders

7 CSS Image Sliders

2 Pure CSS5 with JSPublished 2026-10-05Free · no sign-up

Seven image sliders: a script-free scroll-snap slider, the classic arrows and dots, a cross-fade that can be paused, a product gallery with thumbnails, a slider that peeks at the next slide, a before and after comparison and a vertical story feed.

01. Scroll-Snap Image Slider

SliderNo JS

Slides in a horizontal scroll container with scroll-snap-type, so each swipe or trackpad scroll lands neatly on a slide. It needs no script, works with the arrow keys once focused, and the slides are a labelled list. The images are gradient stand-ins.

View code
<div class="is1" tabindex="0" role="region" aria-label="Photos, scroll sideways"><ul><li style="--g:linear-gradient(135deg,#f59e0b,#b91c1c)">Canyon at dusk</li><li style="--g:linear-gradient(135deg,#22d3ee,#1e40af)">Glacier lake</li><li style="--g:linear-gradient(135deg,#84cc16,#166534)">Rice terraces</li></ul></div>
.is1 { width: 300px; max-width: 100%; overflow-x: auto; scroll-snap-type: x mandatory; border-radius: 16px; scrollbar-width: thin; font-family: Inter, system-ui, sans-serif; }
.is1:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
.is1 ul { display: flex; margin: 0; padding: 0; list-style: none; }
.is1 li { flex: 0 0 100%; display: grid; align-items: end; aspect-ratio: 4 / 3; padding: 14px; box-sizing: border-box; background: var(--g); color: #fff; font-weight: 700; text-shadow: 0 1px 3px rgba(0,0,0,.6); scroll-snap-align: start; }

02. Slider with Arrows and Dots

SliderJavaScript

The classic slider: previous and next arrows and a row of dot buttons. The dots are buttons named Slide 1, 2 and 3 with aria-current on the active one, and a visually hidden live region announces the slide number politely.

View code
<section class="is2" aria-roledescription="carousel" aria-label="Featured rooms"><div class="vp"><div class="tr"><div class="s" style="--g:linear-gradient(135deg,#fde68a,#f97316)" role="group" aria-roledescription="slide" aria-label="1 of 3">Sunroom</div><div class="s" style="--g:linear-gradient(135deg,#a7f3d0,#0d9488)" role="group" aria-roledescription="slide" aria-label="2 of 3" inert>Garden studio</div><div class="s" style="--g:linear-gradient(135deg,#c7d2fe,#6d28d9)" role="group" aria-roledescription="slide" aria-label="3 of 3" inert>Loft bedroom</div></div></div><div class="ctl"><button type="button" class="pv" aria-label="Previous slide">&larr;</button><div class="dots"><button type="button" aria-label="Slide 1" aria-current="true"></button><button type="button" aria-label="Slide 2"></button><button type="button" aria-label="Slide 3"></button></div><button type="button" class="nx" aria-label="Next slide">&rarr;</button></div><p class="sr" aria-live="polite"></p></section>
.is2 { width: 300px; max-width: 100%; font-family: Inter, system-ui, sans-serif; }
.is2 .vp { overflow: hidden; border-radius: 14px; }
.is2 .tr { display: flex; transition: transform .4s ease; }
.is2 .s { flex: 0 0 100%; display: grid; align-items: end; aspect-ratio: 16 / 10; padding: 14px; box-sizing: border-box; background: var(--g); color: #1f2937; font-weight: 800; }
.is2 .ctl { display: flex; align-items: center; justify-content: space-between; margin-top: 10px; }
.is2 .ctl > button { width: 38px; height: 38px; border: 1px solid #d0d5dd; border-radius: 50%; background: #fff; color: #101828; font-size: 16px; cursor: pointer; }
.is2 .dots { display: flex; gap: 8px; }
.is2 .dots button { width: 10px; height: 10px; padding: 0; border: 0; border-radius: 50%; background: #98a2b3; cursor: pointer; }
.is2 .dots [aria-current] { width: 24px; border-radius: 5px; background: #101828; }
.is2 button:focus-visible { outline: 2px solid #528bff; outline-offset: 2px; }
.is2 .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
@media (prefers-reduced-motion: reduce) { .is2 .tr { transition: none; } }
var r = document.querySelector('.is2'), tr = r.querySelector('.tr'), ss = r.querySelectorAll('.s'), dots = r.querySelectorAll('.dots button'), live = r.querySelector('.sr'), i = 0, n = dots.length;
function go(k) {
  i = (k + n) % n; tr.style.transform = 'translateX(' + (-100 * i) + '%)';
  ss.forEach(function (s, j) { s.inert = j !== i; });
  dots.forEach(function (d, j) { if (j === i) d.setAttribute('aria-current', 'true'); else d.removeAttribute('aria-current'); });
  live.textContent = 'Slide ' + (i + 1) + ' of ' + n;
}
r.querySelector('.pv').onclick = function () { go(i - 1); };
r.querySelector('.nx').onclick = function () { go(i + 1); };
dots.forEach(function (d, j) { d.onclick = function () { go(j); }; });

03. Cross-Fade Slider with Pause

SliderFade

Slides stacked in one grid cell fade into each other instead of sliding. Only the active slide is reachable by assistive technology because the others are inert, and the Pause button stops the automatic rotation, which any auto-advancing slider must offer. It starts paused for reduced motion.

View code
<section class="is3" aria-label="Seasonal looks"><div class="st"><div class="s on" style="--g:linear-gradient(135deg,#e11d48,#6d28d9)">Spring</div><div class="s" style="--g:linear-gradient(135deg,#ca8a04,#c2410c)" inert>Summer</div><div class="s" style="--g:linear-gradient(135deg,#b45309,#451a03)" inert>Autumn</div></div><button type="button" aria-pressed="false">Pause</button></section>
.is3 { position: relative; width: 300px; max-width: 100%; font-family: Inter, system-ui, sans-serif; }
.is3 .st { display: grid; border-radius: 14px; overflow: hidden; }
.is3 .s { grid-area: 1 / 1; display: grid; place-items: center; aspect-ratio: 16 / 10; background: var(--g); color: #fff; font-size: 26px; font-weight: 800; opacity: 0; transition: opacity .8s ease; }
.is3 .s.on { opacity: 1; }
.is3 button { position: absolute; right: 10px; bottom: 10px; padding: 6px 12px; border: 0; border-radius: 999px; background: rgba(0,0,0,.6); color: #fff; font: 600 13px Inter, system-ui, sans-serif; cursor: pointer; }
.is3 button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .is3 .s { transition: none; } }
var ss = document.querySelectorAll('.is3 .s'), b = document.querySelector('.is3 button'), i = 0, paused = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (paused) { b.setAttribute('aria-pressed', 'true'); b.textContent = 'Play'; }
setInterval(function () {
  if (paused) return;
  ss[i].classList.remove('on'); ss[i].inert = true;
  i = (i + 1) % ss.length; ss[i].classList.add('on'); ss[i].inert = false;
}, 3000);
b.onclick = function () { paused = !paused; b.setAttribute('aria-pressed', paused); b.textContent = paused ? 'Play' : 'Pause'; };

04. Slider with Thumbnail Strip

SliderGallery

A product gallery: a large image and a row of thumbnail buttons below it. Each thumbnail is a button with aria-pressed, and choosing one swaps the large image and its accessible name. The thumbnail row scrolls sideways when there are more than fit.

View code
<div class="is4"><div class="big" style="--g:linear-gradient(135deg,#e0e7ff,#818cf8)" role="img" aria-label="Lamp, front view"></div><div class="th"><button type="button" aria-pressed="true" style="--g:linear-gradient(135deg,#e0e7ff,#818cf8)" data-l="Lamp, front view" aria-label="Front view"></button><button type="button" aria-pressed="false" style="--g:linear-gradient(135deg,#fef3c7,#f59e0b)" data-l="Lamp, switched on" aria-label="Switched on"></button><button type="button" aria-pressed="false" style="--g:linear-gradient(135deg,#dcfce7,#22c55e)" data-l="Lamp, in a living room" aria-label="In a living room"></button><button type="button" aria-pressed="false" style="--g:linear-gradient(135deg,#fce7f3,#db2777)" data-l="Lamp, base detail" aria-label="Base detail"></button></div></div>
.is4 { width: 280px; max-width: 100%; }
.is4 .big { aspect-ratio: 1; border-radius: 14px; background: var(--g); }
.is4 .th { display: flex; gap: 8px; margin-top: 10px; overflow-x: auto; padding: 3px; }
.is4 .th button { flex: 0 0 56px; height: 56px; border: 2px solid transparent; border-radius: 10px; background: var(--g); cursor: pointer; }
.is4 .th [aria-pressed="true"] { border-color: #101828; }
.is4 .th button:focus-visible { outline: 2px solid #528bff; outline-offset: 2px; }
var big = document.querySelector('.is4 .big'), bs = document.querySelectorAll('.is4 .th button');
bs.forEach(function (b) { b.onclick = function () {
  bs.forEach(function (x) { x.setAttribute('aria-pressed', x === b); });
  big.style.setProperty('--g', b.style.getPropertyValue('--g')); big.setAttribute('aria-label', b.dataset.l);
}; });

05. Peek Slider Showing the Next Slide

SliderMobile

Slides are 82 percent of the width, so part of the next one peeks in from the side, a clear hint that there is more to swipe. scroll-snap-align: center with side margins keeps the active slide centred with its neighbours visible.

View code
<ul class="is5" tabindex="0" aria-label="Recipes, scroll sideways"><li style="--g:linear-gradient(135deg,#c2410c,#7c2d12)">Smoky shakshuka</li><li style="--g:linear-gradient(135deg,#047857,#064e3b)">Green curry</li><li style="--g:linear-gradient(135deg,#a16207,#713f12)">Saffron risotto</li><li style="--g:linear-gradient(135deg,#be185d,#831843)">Beetroot salad</li></ul>
.is5 { display: flex; gap: 10px; width: 320px; max-width: 100%; margin: 0; padding: 4px 0; list-style: none; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; font-family: Inter, system-ui, sans-serif; }
.is5::-webkit-scrollbar { display: none; }
.is5:focus-visible { outline: 2px solid #fff; outline-offset: 2px; border-radius: 14px; }
.is5 li { flex: 0 0 82%; display: grid; align-items: end; height: 190px; padding: 14px; box-sizing: border-box; border-radius: 14px; background: var(--g); color: #fff; font-weight: 800; scroll-snap-align: center; }
.is5 li:first-child { margin-left: 9%; } .is5 li:last-child { margin-right: 9%; }

06. Before and After Comparison Slider

SliderRange

Drag the handle to compare a before and after image. The handle is a native range input stretched invisibly over the image, so it works with arrow keys and screen readers out of the box, and its value sets the clip-path of the top image.

View code
<div class="is6"><div class="a" aria-hidden="true">Before</div><div class="b" aria-hidden="true">After</div><input type="range" min="0" max="100" value="50" aria-label="Before and after split, percent"></div>
.is6 { --p: 50%; position: relative; width: 300px; max-width: 100%; aspect-ratio: 4 / 3; border-radius: 14px; overflow: hidden; font: 800 14px Inter, system-ui, sans-serif; }
.is6 .a, .is6 .b { position: absolute; inset: 0; display: flex; padding: 12px; }
.is6 .a { background: linear-gradient(135deg, #9ca3af, #4b5563); color: #fff; align-items: flex-start; }
.is6 .b { background: linear-gradient(135deg, #fbbf24, #0ea5e9); color: #0c4a6e; justify-content: flex-end; clip-path: inset(0 0 0 var(--p)); }
.is6::after { content: ""; position: absolute; top: 0; bottom: 0; left: var(--p); width: 3px; margin-left: -1.5px; background: #fff; box-shadow: 0 0 6px rgba(0,0,0,.4); pointer-events: none; }
.is6 input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: ew-resize; }
.is6:has(input:focus-visible) { outline: 3px solid #528bff; outline-offset: 3px; }
var w = document.querySelector('.is6'), r = w.querySelector('input');
r.addEventListener('input', function () { w.style.setProperty('--p', r.value + '%'); });

07. Vertical Story Slider

SliderVertical

A full-height vertical slider, like short-video or story feeds: one slide per screen with scroll-snap on the y axis. Progress bars at the top show how many stories there are, and the filled ones update from an IntersectionObserver.

View code
<div class="is7"><div class="bars" aria-hidden="true"><i class="on"></i><i></i><i></i></div><div class="v" tabindex="0" role="region" aria-label="Stories, scroll down"><section style="--g:linear-gradient(160deg,#6366f1,#0f172a)">New collection drops Friday</section><section style="--g:linear-gradient(160deg,#db2777,#0f172a)">Behind the scenes at the studio</section><section style="--g:linear-gradient(160deg,#0d9488,#0f172a)">How we pack your order</section></div></div>
.is7 { position: relative; width: 200px; max-width: 100%; height: 310px; border-radius: 18px; overflow: hidden; font: 800 18px/1.25 Inter, system-ui, sans-serif; }
.is7 .bars { position: absolute; top: 10px; left: 10px; right: 10px; display: flex; gap: 4px; z-index: 1; }
.is7 .bars i { flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,.35); }
.is7 .bars i.on { background: #fff; }
.is7 .v { height: 100%; overflow-y: auto; scroll-snap-type: y mandatory; scrollbar-width: none; }
.is7 .v::-webkit-scrollbar { display: none; }
.is7 .v:focus-visible { outline: 3px solid #fff; outline-offset: -3px; }
.is7 section { display: grid; align-items: end; height: 100%; padding: 18px; box-sizing: border-box; background: var(--g); color: #fff; scroll-snap-align: start; }
var bars = document.querySelectorAll('.is7 .bars i'), secs = document.querySelectorAll('.is7 section');
var io = new IntersectionObserver(function (es) { es.forEach(function (e) { if (e.isIntersecting) { var k = [].indexOf.call(secs, e.target); bars.forEach(function (b, j) { b.classList.toggle('on', j <= k); }); } }); }, { root: document.querySelector('.is7 .v'), threshold: .6 });
secs.forEach(function (s) { io.observe(s); });

About these CSS Image Sliders

Start with scroll-snap

A horizontal scroll container with scroll-snap-type: x mandatory already swipes on phones, scrolls with trackpads and needs no JavaScript. Add buttons on top only if you need them.

Label the carousel

Give the slider a name (aria-label="Featured rooms"), mark slides as groups named 1 of 3, and announce changes politely. Hidden slides should be inert so focus cannot land inside them.

Auto-play needs a pause button

Content that moves on its own for more than five seconds must have a way to pause it. Better still, do not auto-advance at all, or start paused when reduced motion is requested.

Use native controls where you can

The comparison slider is a stretched <input type="range">, so arrow keys, touch and screen readers work without any custom handling.

Frequently asked questions

Are image carousels bad for conversions?

Auto-rotating hero carousels often are, because people miss slides. Swipeable galleries of related images work well.

How do I add real images?

Replace each gradient slide with an img that has width, height and alt attributes, and lazy-load the ones off screen.

Does the slider need a library?

No. The heaviest demo uses about ten lines of script.