Swiper Testimonial Fade Slider — Free JS Snippet
Swiper Testimonial Slider with Fade and Avatar Pagination · Carousels · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Swiper Testimonial Slider — HTML, CSS & JavaScript

Testimonials are read, not skimmed, so the way they change matters. A sliding carousel is jumpy for text — the eye follows the motion instead of the words — while a quiet cross-fade lets a reader finish a quote without a distraction and then see the next arrive in place. Swiper's fade effect is designed for this, and this snippet combines it with three details that separate a polished testimonial block from a default one.
The first is crossFade. effect: 'fade' on its own fades slides in and out one after another using opacity, which can leave both partly visible at the same time — two quotes overlapping mid-transition, unreadable. Setting fadeEffect: { crossFade: true } makes the outgoing slide fade out as the incoming one fades in, so exactly one is fully visible at any moment and the text never superimposes. It is the difference between a smooth swap and a visible glitch, and it is an option many implementations never turn on.
The second is autoHeight. Quotes vary in length, and fade slides all sit on top of each other in the same space, so the container would normally be as tall as the tallest quote and leave a hole under short ones. autoHeight: true resizes the container to the active slide. The third is the pagination. Rather than dots, the snippet builds a row of avatar buttons — coloured circles with initials — because putting a face to each quote is what makes social proof feel real, and it lets a reader jump to the person they care about. The custom buttons call slideTo() and keep aria-selected in step through the slideChange event, and they use role="tab" so assistive technology treats them as a set of choices.
Autoplay is configured for reading. The delay is a generous six seconds, pauseOnMouseEnter holds the current quote while the pointer is over it, and disableOnInteraction: true means that once a visitor deliberately picks a testimonial, autoplay stops for good rather than pulling them away. Each slide is a real figure with a blockquote and figcaption, so the markup is semantic, the stars carry an aria-label, and the quotes are indexed as text.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Ask an AI assistant like Claude to add company logos to each quote, a progress bar under the active avatar, or structured data markup for reviews.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a testimonial slider with Swiper 11 loaded from a CDN (bundle script and CSS).
Requirements:
- Use effect 'fade' with fadeEffect { crossFade: true }, autoHeight: true, speed 600, autoplay { delay: 6000, disableOnInteraction: true, pauseOnMouseEnter: true }, keyboard and a11y.
- Render five quotes as figure/blockquote/figcaption with a star rating (aria-label) and a coloured initials avatar.
- Build custom pagination as avatar buttons with role="tab" and aria-selected; clicking calls slideTo() and stops autoplay, and the slideChange event updates aria-selected.
- Scale and outline the active avatar, and give buttons visible focus outlines.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Source Code
<div class="tt-wrap">
<div class="swiper tt-swiper" id="ttSwiper">
<div class="swiper-wrapper" id="ttSlides"></div>
</div>
<div class="tt-pag" id="ttPag" role="tablist" aria-label="Choose a testimonial"></div>
</div>body { background: linear-gradient(160deg, #eef2ff, #fdf2f8); padding: 26px 16px; font-family: system-ui, sans-serif; }
.tt-wrap { max-width: 620px; margin: 0 auto; text-align: center; }
.tt-swiper { overflow: hidden; }
.tt-swiper .swiper-slide { padding: 6px 22px 10px; }
.tt-quote { position: relative; background: #fff; border: 1px solid #e3e6f5; border-radius: 20px; padding: 34px 32px 28px; box-shadow: 0 14px 34px rgba(60,50,140,.1); }
.tt-quote::before { content: '\201C'; position: absolute; top: -6px; left: 22px; font: 800 82px/1 Georgia, serif; color: #c7d2fe; }
.tt-stars { color: #f59e0b; letter-spacing: 3px; font-size: 16px; }
.tt-quote p { margin: 12px 0 18px; font-size: 19px; line-height: 1.6; color: #1e2140; font-weight: 500; }
.tt-who { display: flex; align-items: center; justify-content: center; gap: 12px; }
.tt-av { width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center; color: #fff; font: 800 15px/1 system-ui, sans-serif; }
.tt-who b { display: block; font-size: 14.5px; color: #1e2140; text-align: left; } .tt-who span { display: block; font-size: 12.5px; color: #6b7290; text-align: left; }
.tt-pag { display: flex; justify-content: center; gap: 12px; margin-top: 20px; }
.tt-pag button { position: relative; width: 46px; height: 46px; border-radius: 50%; border: 3px solid transparent; padding: 0; color: #fff; font: 800 13px/1 system-ui, sans-serif; cursor: pointer; opacity: .55; filter: saturate(.7); transition: opacity .25s, transform .25s, border-color .25s, filter .25s; }
.tt-pag button:hover { opacity: .85; }
.tt-pag button[aria-selected="true"] { opacity: 1; filter: none; transform: scale(1.14); border-color: #fff; box-shadow: 0 0 0 3px #818cf8, 0 8px 16px rgba(60,50,140,.25); }
.tt-pag button:focus-visible { outline: 3px solid #4f46e5; outline-offset: 3px; }const VOICES = [
{ q: 'We replaced three tools with this one and our onboarding time dropped from two weeks to three days.', n: 'Priya Raman', r: 'Head of Ops, Northwind', c: '#6366f1', i: 'PR' },
{ q: 'The API is the first I have used that felt designed by people who actually ship products on it.', n: 'Marcus Lee', r: 'Staff Engineer, Acme Labs', c: '#0ea5e9', i: 'ML' },
{ q: 'Support answered in eleven minutes on a Sunday. That single interaction sold our whole leadership team.', n: 'Sofia Alvarez', r: 'COO, Globex', c: '#ec4899', i: 'SA' },
{ q: 'Our conversion rate went up 18% the week we switched. I stopped questioning it after that.', n: 'Tomas Novak', r: 'Growth Lead, Initech', c: '#10b981', i: 'TN' },
{ q: 'Beautiful defaults, sensible escape hatches. It is a joy to build on.', n: 'Aiko Tanaka', r: 'Design Systems, Hooli', c: '#f59e0b', i: 'AT' },
];
document.getElementById('ttSlides').innerHTML = VOICES.map(function (v) {
return '<div class="swiper-slide"><figure class="tt-quote" style="margin:0"><div class="tt-stars" aria-label="5 out of 5 stars">★★★★★</div>' +
'<blockquote style="margin:0"><p>' + v.q + '</p></blockquote>' +
'<figcaption class="tt-who"><span class="tt-av" style="background:' + v.c + '">' + v.i + '</span><span><b>' + v.n + '</b><span>' + v.r + '</span></span></figcaption></figure></div>';
}).join('');
const pag = document.getElementById('ttPag');
pag.innerHTML = VOICES.map(function (v, i) {
return '<button type="button" role="tab" aria-selected="' + (i === 0) + '" aria-label="' + v.n + '" style="background:' + v.c + '" data-i="' + i + '">' + v.i + '</button>';
}).join('');
const swiper = new Swiper('#ttSwiper', {
effect: 'fade',
fadeEffect: { crossFade: true }, // without this, both slides show at partial opacity and text overlaps mid-transition
speed: 600,
autoHeight: true, // quotes differ in length; let the container resize instead of leaving gaps
autoplay: { delay: 6000, disableOnInteraction: true, pauseOnMouseEnter: true },
keyboard: { enabled: true },
a11y: { enabled: true },
});
// Custom pagination: our own avatar buttons, driven by slideTo and the slideChange event.
pag.addEventListener('click', function (e) {
const b = e.target.closest('button'); if (!b) return;
swiper.autoplay.stop();
swiper.slideTo(Number(b.dataset.i));
});
swiper.on('slideChange', function () {
pag.querySelectorAll('button').forEach(function (b, i) { b.setAttribute('aria-selected', String(i === swiper.activeIndex)); });
});Step by step
How to Use
- 1Watch it rotateTestimonials cross-fade every six seconds. Only one is ever fully visible.
- 2Pick a personClick an avatar below. The slider jumps to that quote and autoplay stops.
- 3Hover to pauseHover a quote to hold it on screen while you read.
- 4Notice the heightThe container resizes to fit each quote, so there is no empty space under short ones.
- 5Use the keyboardPress the arrow keys to move between testimonials, or Tab to the avatar buttons.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Fade effect alone leaves both slides partly visible. Set fadeEffect: { crossFade: true } so the outgoing slide fades as the incoming one appears.
Fade slides stack in the same space. Use autoHeight: true so the container resizes to the active slide.
Render your own buttons, call swiper.slideTo(index) on click, and update their state in the slideChange event.
Once a visitor picks a testimonial or drags, autoplay stops permanently so the slider does not pull them away.
Use figure, blockquote and figcaption, give the pagination role="tab" with aria-selected, and label the star rating.
Long enough to read the quote, typically five to eight seconds, and always with a way to pause.
Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from Swiper, so in a framework project install it with npm install swiper (its React and Vue components take the same options) instead of the CDN tag, use the Swiper and SwiperSlide components, or create it in useEffect / onMounted / ngAfterViewInit, and release it with destroy() when the component unmounts.



