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

~/css/tailwind-image-carousels

6 Tailwind Image Carousels

1 Tailwind5 with JSPublished 2026-10-05Free · no sign-up

Six carousels in Tailwind: a scroll-snap carousel with arrows, a sliding track with dots, an auto-rotating fade with a pause button, slides that peek in from the side, a two-up product row and a row with a progress bar.

01. Tailwind Scroll-Snap Carousel with Arrows

TailwindCarouselScroll snap

A swipeable carousel built on native scrolling with snap-x snap-mandatory, plus previous and next buttons that scroll by one slide. Each slide is labelled with its position, such as 2 of 4.

View code
<section aria-roledescription="carousel" aria-label="Featured trips" class="w-full max-w-xs">
  <ul id="twcr1" tabindex="0" aria-label="Featured trips, scroll sideways" class="flex snap-x snap-mandatory overflow-x-auto rounded-2xl [scrollbar-width:none] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 [&::-webkit-scrollbar]:hidden">
    <li role="group" aria-roledescription="slide" aria-label="1 of 4" class="grid aspect-[4/3] w-full shrink-0 snap-start content-end bg-[linear-gradient(#fdba74,#be123c)] p-4 text-lg font-bold text-white">Lisbon</li>
    <li role="group" aria-roledescription="slide" aria-label="2 of 4" class="grid aspect-[4/3] w-full shrink-0 snap-start content-end bg-[linear-gradient(#7dd3fc,#1e3a8a)] p-4 text-lg font-bold text-white">Tromsø</li>
    <li role="group" aria-roledescription="slide" aria-label="3 of 4" class="grid aspect-[4/3] w-full shrink-0 snap-start content-end bg-[linear-gradient(#86efac,#14532d)] p-4 text-lg font-bold text-white">Kyoto</li>
    <li role="group" aria-roledescription="slide" aria-label="4 of 4" class="grid aspect-[4/3] w-full shrink-0 snap-start content-end bg-[linear-gradient(#c4b5fd,#581c87)] p-4 text-lg font-bold text-white">Oaxaca</li>
  </ul>
  <div class="mt-2 flex justify-end gap-2"><button type="button" data-dir="-1" aria-label="Previous slide" class="grid size-9 place-items-center rounded-full bg-white text-slate-800 ring-1 ring-slate-300 hover:bg-slate-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-indigo-600">‹</button><button type="button" data-dir="1" aria-label="Next slide" class="grid size-9 place-items-center rounded-full bg-white text-slate-800 ring-1 ring-slate-300 hover:bg-slate-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-indigo-600">›</button></div>
</section>
var t = document.getElementById('twcr1');
document.querySelectorAll('[data-dir]').forEach(function (b) { b.onclick = function () { t.scrollBy({ left: b.dataset.dir * t.clientWidth, behavior: matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth' }); }; });

02. Tailwind Carousel with Dot Navigation

TailwindCarouselDots

Tips slide along a track moved with translate, and dot buttons jump between them; the current dot has aria-current. Slides that are out of view are made inert and hidden from assistive technology.

View code
<section aria-roledescription="carousel" aria-label="Store tips" class="w-full max-w-xs">
  <div class="overflow-hidden rounded-2xl">
    <ul id="twcr2" class="flex transition-transform duration-500 motion-reduce:transition-none">
      <li role="group" aria-roledescription="slide" aria-label="1 of 3" class="w-full shrink-0 bg-slate-800 p-5 text-slate-100"><p class="text-xs font-bold uppercase tracking-wider text-sky-300">Tip 1</p><p class="mt-1 text-sm">Keep product titles under 60 characters so they fit in search results.</p></li>
      <li role="group" aria-roledescription="slide" aria-label="2 of 3" class="w-full shrink-0 bg-slate-800 p-5 text-slate-100"><p class="text-xs font-bold uppercase tracking-wider text-sky-300">Tip 2</p><p class="mt-1 text-sm">Put the returns policy one click from every product page.</p></li>
      <li role="group" aria-roledescription="slide" aria-label="3 of 3" class="w-full shrink-0 bg-slate-800 p-5 text-slate-100"><p class="text-xs font-bold uppercase tracking-wider text-sky-300">Tip 3</p><p class="mt-1 text-sm">Test checkout on a phone before every launch.</p></li>
    </ul>
  </div>
  <div id="twcr2d" class="mt-3 flex justify-center gap-2"><button type="button" aria-label="Show slide 1" class="size-2.5 rounded-full bg-slate-600 aria-[current=true]:w-6 aria-[current=true]:bg-sky-400 transition-all focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-300"></button><button type="button" aria-label="Show slide 2" class="size-2.5 rounded-full bg-slate-600 aria-[current=true]:w-6 aria-[current=true]:bg-sky-400 transition-all focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-300"></button><button type="button" aria-label="Show slide 3" class="size-2.5 rounded-full bg-slate-600 aria-[current=true]:w-6 aria-[current=true]:bg-sky-400 transition-all focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-300"></button></div>
</section>
var track = document.getElementById('twcr2'), dots = document.querySelectorAll('#twcr2d button'), slides = track.children;
function go(i) { track.style.transform = 'translateX(-' + i * 100 + '%)'; [].forEach.call(slides, function (s, k) { s.inert = k !== i; s.setAttribute('aria-hidden', k !== i); }); dots.forEach(function (d, k) { if (k === i) d.setAttribute('aria-current', 'true'); else d.removeAttribute('aria-current'); }); }
dots.forEach(function (d, k) { d.onclick = function () { go(k); }; });
go(0);

03. Tailwind Auto-Rotating Fade Carousel

TailwindCarouselAutoplay

Slides cross-fade every four seconds, with a Pause button that must be there for anything that moves on its own. Autoplay never starts for people who prefer reduced motion, and the slide region becomes polite live only while paused.

View code
<section aria-roledescription="carousel" aria-label="Seasonal picks" class="w-full max-w-xs">
  <div id="twcr3" aria-live="off" class="relative aspect-[4/3] overflow-hidden rounded-2xl">
    <div role="group" aria-roledescription="slide" aria-label="1 of 3" class="absolute inset-0 grid content-end bg-[linear-gradient(#fde68a,#ea580c)] p-4 text-lg font-bold text-white opacity-100 transition-opacity duration-700 motion-reduce:transition-none">Autumn knits</div>
    <div role="group" aria-roledescription="slide" aria-label="2 of 3" class="absolute inset-0 grid content-end bg-[linear-gradient(#e0f2fe,#1d4ed8)] p-4 text-lg font-bold text-white opacity-0 transition-opacity duration-700 motion-reduce:transition-none">Rain jackets</div>
    <div role="group" aria-roledescription="slide" aria-label="3 of 3" class="absolute inset-0 grid content-end bg-[linear-gradient(#dcfce7,#15803d)] p-4 text-lg font-bold text-white opacity-0 transition-opacity duration-700 motion-reduce:transition-none">Garden tools</div>
  </div>
  <button id="twcr3p" type="button" class="mt-2 rounded-lg bg-slate-900 px-3 py-1.5 text-sm font-semibold text-white hover:bg-slate-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-900">Pause</button>
</section>
var box = document.getElementById('twcr3'), s = box.children, p = document.getElementById('twcr3p'), i = 0, t = null;
function show(n) { [].forEach.call(s, function (el, k) { el.style.opacity = k === n ? 1 : 0; el.setAttribute('aria-hidden', k !== n); }); }
function play() { t = setInterval(function () { i = (i + 1) % s.length; show(i); }, 4000); p.textContent = 'Pause'; box.setAttribute('aria-live', 'off'); }
function pause() { clearInterval(t); t = null; p.textContent = 'Play'; box.setAttribute('aria-live', 'polite'); }
p.onclick = function () { if (t) pause(); else play(); };
show(0); if (matchMedia('(prefers-reduced-motion: reduce)').matches) pause(); else play();

04. Tailwind Peeking Card Carousel

TailwindCarouselPeek

Each slide is 80% of the width so the next one peeks in from the side, a clear hint that the row can be swiped. snap-center centres each slide and scroll-px keeps the first and last from touching the edge.

View code
<ul aria-label="Recipes" tabindex="0" class="flex w-full max-w-xs snap-x snap-mandatory gap-3 overflow-x-auto scroll-px-4 px-4 pb-2 [scrollbar-width:thin]">
  <li class="w-[80%] shrink-0 snap-center rounded-2xl bg-amber-100 p-4"><h3 class="font-bold text-amber-950">Shakshuka</h3><p class="text-sm text-amber-900">25 minutes · serves 2</p></li>
  <li class="w-[80%] shrink-0 snap-center rounded-2xl bg-emerald-100 p-4"><h3 class="font-bold text-emerald-950">Green curry</h3><p class="text-sm text-emerald-900">35 minutes · serves 4</p></li>
  <li class="w-[80%] shrink-0 snap-center rounded-2xl bg-rose-100 p-4"><h3 class="font-bold text-rose-950">Berry crumble</h3><p class="text-sm text-rose-900">40 minutes · serves 6</p></li>
</ul>
ul[aria-label="Recipes"]:focus-visible { outline: 2px solid #4f46e5; outline-offset: 2px; }

05. Tailwind Two-Up Product Carousel

TailwindCarouselProducts

Two product cards visible at once with arrow buttons that disable themselves at either end. The script reads the scroll position after every scroll, so swiping and the buttons always agree.

View code
<section aria-label="New arrivals" class="w-full max-w-xs">
  <div class="flex items-center justify-between"><h3 class="font-bold text-slate-900">New arrivals</h3><div class="flex gap-1"><button id="twcr5a" type="button" aria-label="Previous products" class="grid size-8 place-items-center rounded-full ring-1 ring-slate-300 hover:bg-slate-100 disabled:opacity-40 focus-visible:outline focus-visible:outline-2 focus-visible:outline-indigo-600">‹</button><button id="twcr5b" type="button" aria-label="Next products" class="grid size-8 place-items-center rounded-full ring-1 ring-slate-300 hover:bg-slate-100 disabled:opacity-40 focus-visible:outline focus-visible:outline-2 focus-visible:outline-indigo-600">›</button></div></div>
  <ul id="twcr5" tabindex="0" aria-label="New arrivals, scroll sideways" class="mt-2 flex snap-x snap-mandatory gap-3 overflow-x-auto [scrollbar-width:none] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 [&::-webkit-scrollbar]:hidden">
    <li class="w-[calc(50%-6px)] shrink-0 snap-start"><div role="img" aria-label="Sage linen shirt" class="aspect-square rounded-xl bg-[linear-gradient(#d9f99d,#4d7c0f)]"></div><p class="mt-1 text-sm font-semibold text-slate-900">Linen shirt</p><p class="text-xs text-slate-600">$58</p></li>
    <li class="w-[calc(50%-6px)] shrink-0 snap-start"><div role="img" aria-label="Rust wool scarf" class="aspect-square rounded-xl bg-[linear-gradient(#fed7aa,#c2410c)]"></div><p class="mt-1 text-sm font-semibold text-slate-900">Wool scarf</p><p class="text-xs text-slate-600">$34</p></li>
    <li class="w-[calc(50%-6px)] shrink-0 snap-start"><div role="img" aria-label="Navy canvas tote" class="aspect-square rounded-xl bg-[linear-gradient(#bfdbfe,#1e3a8a)]"></div><p class="mt-1 text-sm font-semibold text-slate-900">Canvas tote</p><p class="text-xs text-slate-600">$32</p></li>
    <li class="w-[calc(50%-6px)] shrink-0 snap-start"><div role="img" aria-label="Plum beanie" class="aspect-square rounded-xl bg-[linear-gradient(#f5d0fe,#86198f)]"></div><p class="mt-1 text-sm font-semibold text-slate-900">Beanie</p><p class="text-xs text-slate-600">$22</p></li>
  </ul>
</section>
var t = document.getElementById('twcr5'), a = document.getElementById('twcr5a'), b = document.getElementById('twcr5b');
function upd() { a.disabled = t.scrollLeft <= 2; b.disabled = t.scrollLeft >= t.scrollWidth - t.clientWidth - 2; }
a.onclick = function () { t.scrollBy({ left: -t.clientWidth, behavior: 'smooth' }); };
b.onclick = function () { t.scrollBy({ left: t.clientWidth, behavior: 'smooth' }); };
t.addEventListener('scroll', upd); upd();

06. Tailwind Carousel with Scroll Progress Bar

TailwindCarouselProgress

A swipeable row of slides with a thin bar underneath that fills as you scroll through them. The bar width is scrollLeft divided by the scrollable distance, updated on every scroll event, and it is decorative only.

View code
<section aria-label="Studio photos" class="w-full max-w-xs">
  <ul id="twcr6" tabindex="0" aria-label="Studio photos, scroll sideways" class="flex snap-x snap-mandatory gap-2 overflow-x-auto rounded-xl [scrollbar-width:none] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-300 [&::-webkit-scrollbar]:hidden">
    <li class="relative aspect-[3/4] w-40 shrink-0 snap-start rounded-xl bg-[linear-gradient(#fda4af,#9f1239)]"><span class="sr-only">Pottery wheel</span></li>
    <li class="relative aspect-[3/4] w-40 shrink-0 snap-start rounded-xl bg-[linear-gradient(#93c5fd,#1e40af)]"><span class="sr-only">Glaze shelf</span></li>
    <li class="relative aspect-[3/4] w-40 shrink-0 snap-start rounded-xl bg-[linear-gradient(#fde68a,#a16207)]"><span class="sr-only">Kiln room</span></li>
    <li class="relative aspect-[3/4] w-40 shrink-0 snap-start rounded-xl bg-[linear-gradient(#a7f3d0,#047857)]"><span class="sr-only">Finished bowls</span></li>
  </ul>
  <div aria-hidden="true" class="mt-3 h-1 overflow-hidden rounded-full bg-slate-700"><div id="twcr6b" class="h-full w-0 rounded-full bg-sky-400"></div></div>
</section>
var t = document.getElementById('twcr6'), bar = document.getElementById('twcr6b');
function upd() { var max = t.scrollWidth - t.clientWidth; bar.style.width = (max ? t.scrollLeft / max * 100 : 100) + '%'; }
t.addEventListener('scroll', upd); upd();

About these Tailwind Image Carousels

Native scrolling first

flex snap-x snap-mandatory overflow-x-auto gives swipe, trackpad and keyboard scrolling for free; buttons only add scrollBy.

Label slides and the carousel

Use aria-roledescription="carousel" with a label, and label each slide with its position such as 2 of 4.

Anything that moves needs pause

Autoplay must have a visible pause control, and should not start at all for people who prefer reduced motion.

Out-of-view slides

When slides are moved with transforms, make hidden ones inert so focus never lands off-screen.

Frequently asked questions

Which demos need JavaScript?

The arrows, dots, autoplay and progress bar; the peeking row is CSS only.

Are carousels good for conversions?

Important content should not hide in later slides; show key items without needing to swipe.

Is there a CSS version?

Yes, see the CSS image sliders collection.