You Might Also Like
Testimonial Carousel — Free HTML CSS JS Slider Snippet
Testimonial Carousel · Cards · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Testimonial Carousel — Autoplay Slider with Stars, Avatars and Animated Dots

Social proof sells, but a wall of testimonials is overwhelming and a single static quote is forgettable. The testimonial carousel solves both problems: it shows one customer quote at a time, full and readable, then advances automatically so visitors see several without scrolling. This component is a complete, accessible carousel built with a CSS transform track, autoplay that pauses on interaction, arrow controls, and animated progress dots — all in plain HTML, CSS, and vanilla JavaScript with no slider library.
The transform-based track
The mechanics are a flex track inside an overflow: hidden viewport. Every slide has flex: 0 0 100%, so each one is exactly the viewport's width and they line up in a horizontal row. Sliding is a single transform: translateX(-N%) on the track, where N is the active index times 100 — moving to slide 2 translates the track by -100%, slide 3 by -200%, and so on. Because it animates only transform, the slide runs on the GPU compositor at 60fps with no layout work, and will-change: transform hints the browser to promote the track to its own layer ahead of time.
Data-driven slides and ratings
The carousel is built from a DATA array of { text, name, role, rating, color } objects. On load the script loops the array, builds each slide's markup, and derives two things automatically: the author's initials (by splitting the name and taking each word's first letter) for the gradient avatar, and the star row (by rendering a filled star SVG for each point up to rating out of five). This means adding a testimonial is a single array entry — no markup edits — and the four-star example shows partial ratings render correctly.
Autoplay with pause-on-interaction
A setInterval advances the carousel every 5 seconds by calling goTo(index + 1). The crucial UX detail is that autoplay yields to the user: mouseenter and focusin clear the interval so the carousel stops while someone is reading or tabbing through it, and mouseleave and focusout restart it. Every manual action — clicking an arrow or a dot — also calls restart(), which clears and re-creates the timer so the full 5 seconds is given to the slide the user just chose rather than whatever was left on the previous tick.
Infinite wrap-around
The goTo() function normalises any index with (i + length) % length, so advancing past the last slide wraps to the first and going back from the first wraps to the last. The arrows and autoplay all route through this one function, which means there is a single source of truth for the current index, the track transform, and the active dot — no chance of the dots drifting out of sync with the visible slide.
Animated progress dots
The dots are real <button> elements with role="tab". The active dot is not just a different colour — it stretches from a circle to a pill with width: 24px via a CSS transition on width, giving a subtle, modern progress indicator. Clicking any dot jumps straight to that slide and restarts autoplay. The arrows are circular icon buttons with hover states that adopt the accent colour, and everything carries appropriate aria-label and aria-roledescription attributes so the carousel is understandable to screen readers.
Customisation
To use it, replace the DATA array with your real testimonials — set each rating from 1 to 5, give each author a gradient color, and the avatars and stars render automatically. Adjust the 5000ms autoplay interval, change the 0.5s slide transition duration or easing, and swap the #6366f1 accent (active dot, arrow hover, avatars) for your brand. To show real photos instead of initials, render an <img> in place of the .tc-avatar span. For a multi-card view, change the slide's flex-basis to 50% or 33.33% and step the transform by that amount.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to work out the modulo wrap-around math by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why goTo computes the index as (i plus DATA.length) modulo DATA.length instead of a simple bounds check, and why every navigation path — arrows, dots, and autoplay — funnels through that single function rather than each setting the transform independently. The same assistant can help optimize it — for instance whether pausing on mouseenter/focusin but not on touch interactions leaves a gap on mobile, or whether rebuilding all slides' innerHTML up front instead of lazily is wasteful for a very large testimonial set. It's also useful for extending the carousel: ask it to show two testimonials per view on wide screens, add swipe gesture support for touch devices, or sync the dots with a progress-bar countdown showing time until the next auto-advance. Treat the code less like a finished artifact and more like a starting point for a conversation.
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 an autoplaying "testimonial carousel" in plain HTML, CSS, and JavaScript using a single transform-driven track — no slider library, no framework.
Requirements:
- A viewport with overflow hidden containing a flex track, where every slide has flex: 0 0 100% so slides sit side by side each exactly the viewport's width.
- A single goTo(index) function that normalizes any given index with modulo arithmetic so it wraps correctly in both directions (going past the last slide returns to the first, going before the first wraps to the last), sets the track's transform to translateX of negative index times 100%, and updates which dot indicator is marked active — every other piece of navigation logic (arrows, dots, autoplay) must call this one function rather than duplicating the transform/active-dot logic.
- Build slides and dot indicators dynamically from a single data array of testimonial objects (quote text, name, role, star rating, accent color), deriving each avatar's initials automatically from the name and rendering exactly as many filled stars as the rating value out of five.
- An autoplay timer that advances to the next slide automatically on a fixed interval, where every manual navigation (arrow click or dot click) clears and restarts that timer so the newly selected slide gets the full interval before it advances again.
- Autoplay must pause while the pointer hovers over the carousel or while any element inside it has keyboard focus, and resume when the pointer leaves or focus moves away.
- The active dot indicator must visually grow from a small circle into a wider pill shape via a CSS transition, distinct from the inactive dots.
- Include appropriate ARIA roles and labels (carousel role description, tab roles on the dots, labeled prev/next buttons) so the carousel is understandable via screen reader and keyboard.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.
Step by step
How to Use
- 1Paste the HTML, CSS, and JSA testimonial card renders with a star rating, quote, author, arrow controls, and a row of dots; it begins autoplaying every 5 seconds.
- 2Use the arrowsClick the left/right arrows to move between testimonials; the track slides smoothly and autoplay timing resets.
- 3Click a dotJump directly to any testimonial; the active dot stretches into a pill to mark your position.
- 4Hover to pauseMove the pointer over the carousel (or tab into it) and autoplay pauses so you can read; it resumes when you leave.
- 5Replace the testimonialsEdit the DATA array — each entry's rating drives the stars and the name drives the avatar initials automatically.
- 6Theme and time itSwap the #6366f1 accent and adjust the 5000ms interval and 0.5s slide duration to fit your brand and pace.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
The interval is set in restart() as setInterval(..., 5000) — change 5000 to your preferred milliseconds. To disable autoplay entirely, remove the restart() call at the bottom and the mouseenter/leave/focus handlers, leaving only the arrows and dots for manual control. The slide animation speed is separate: edit the 0.5s transition on .tc-track.
Change the slide's flex-basis from 100% to 50% (two up) or 33.33% (three up) in .tc-slide, and update goTo() to translate by index * (100 / perView) percent. You may also want the dots to represent pages rather than individual slides. The autoplay, wrap-around, and pause logic stay the same.
Every manual navigation calls restart(), which clears and recreates the timer. This gives the slide you just selected the full interval before it advances, rather than the leftover fraction of the previous tick — so the carousel never jumps away a moment after you click. It is a small detail that makes manual control feel responsive.
Replace the <span class="tc-avatar">initials</span> in the slide template with <img class="tc-avatar" src={t.img} alt={t.name}> and add object-fit: cover to the .tc-avatar rule so the photo fills the circle. Add an img field to each DATA entry. Keep the initials version as a fallback for testimonials without a photo.
Keep the index in state and bind the track's transform to translateX(-index*100%). Render DATA with .map/v-for/*ngFor. Run autoplay in an effect that sets an interval and clears it on cleanup (useEffect return, onUnmounted, ngOnDestroy) — this is essential to avoid leaked timers. Pause handlers map to onMouseEnter/onMouseLeave (and onFocus/onBlur) that clear and restart the interval; arrows and dots call your goTo(i) setter.