An HTML testimonial slider you can copy and paste
A responsive testimonial slider in plain HTML and CSS, with a live demo, code to copy, and optional arrow buttons in a few lines of JavaScript.
Published
A testimonial slider shows a few customer quotes side by side and lets the visitor move through the rest. This one is plain HTML and CSS. It needs no library and no build step, and it needs JavaScript only if you want arrow buttons.
What it looks like
Swipe it, scroll it sideways, or press the arrow buttons. The testimonials in it are samples written for this guide.
The code
Paste this where the slider should appear. On a site builder, that is a custom HTML block or a code block.
Testimonial slider: HTML and CSS
<!-- Sample testimonials. Replace them with your customers' own words. -->
<div class="ts">
<div class="ts-track" role="region" aria-label="Customer testimonials" tabindex="0">
<figure class="ts-slide">
<blockquote>“They rebuilt our booking page in a week. Online bookings went from four a week to eleven.”</blockquote>
<figcaption><b>Dana R.</b> Studio owner, Leeds</figcaption>
</figure>
<figure class="ts-slide">
<blockquote>“I expected to wait days for an answer. The reply came in an hour, and it fixed the problem.”</blockquote>
<figcaption><b>Tomás G.</b> Shop manager, Valencia</figcaption>
</figure>
<figure class="ts-slide">
<blockquote>“We had tried two other suppliers. This is the first one that delivered on the day it promised.”</blockquote>
<figcaption><b>Aiko M.</b> Café owner, Osaka</figcaption>
</figure>
<figure class="ts-slide">
<blockquote>“Clear quote, no surprises on the invoice, and the work was finished a day early.”</blockquote>
<figcaption><b>Priya S.</b> Homeowner, Pune</figcaption>
</figure>
<figure class="ts-slide">
<blockquote>“My team was using it properly within a morning. Nobody asked for training.”</blockquote>
<figcaption><b>Marcus L.</b> Operations lead, Austin</figcaption>
</figure>
</div>
</div>
<style>
.ts {
--ts-accent: #2440d9; /* focus ring and buttons */
--ts-card: #ffffff; /* card background */
--ts-line: #d5d9e4; /* card border */
--ts-text: #1b2033; /* card text */
--ts-gap: 1rem; /* space between cards */
container-type: inline-size;
max-width: 64rem;
margin: 0 auto;
}
.ts-track {
--ts-show: 1; /* cards in view */
display: flex;
gap: var(--ts-gap);
overflow-x: auto;
scroll-snap-type: x mandatory;
padding: 4px 4px 1rem; /* room for the focus ring, and for the scrollbar below */
scroll-padding: 0 4px;
}
.ts-track:focus-visible {
outline: 3px solid var(--ts-accent);
outline-offset: 2px;
}
.ts-slide {
flex: 0 0 calc((100% - var(--ts-gap) * (var(--ts-show) - 1)) / var(--ts-show));
box-sizing: border-box;
scroll-snap-align: start;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 1rem;
margin: 0;
padding: 1.5rem;
border: 1px solid var(--ts-line);
border-radius: 12px;
background: var(--ts-card);
color: var(--ts-text);
}
.ts-slide blockquote {
margin: 0;
font-size: 1.0625rem;
line-height: 1.55;
}
.ts-slide figcaption {
font-size: 0.9375rem;
line-height: 1.4;
}
.ts-slide b {
display: block;
}
/* Two cards when the slider is at least 640px wide, three from 960px. */
@container (min-width: 40rem) {
.ts-track { --ts-show: 2; }
}
@container (min-width: 60rem) {
.ts-track { --ts-show: 3; }
}
@media (prefers-reduced-motion: no-preference) {
.ts-track { scroll-behavior: smooth; }
}
</style>How it works
- The row scrolls.
.ts-trackis a row of cards that is wider than its box. A touch screen swipes it, and a mouse or trackpad scrolls it. - Cards snap into place.
scroll-snap-typeon the row andscroll-snap-alignon each card make a scroll stop at the edge of a card, not halfway through one. - It fits the space it is given. One card shows in a narrow space, two from 640 pixels and three from 960. The slider measures its own width, not the screen's, so it also behaves in a sidebar or a narrow column.
- It works from the keyboard. The row takes focus, and the arrow keys scroll it. A screen reader announces it as "Customer testimonials".
- It respects reduced motion. The smooth scroll is switched on only for visitors who have not asked their device to reduce motion.
Make it yours
- Your testimonials. Each one is a
figurewith the quote in ablockquoteand the name in afigcaption. To add one, copy a whole block from<figure class="ts-slide">to</figure>and change the words. Use what the customer wrote, with their agreement. - Colours. Change the four colour values at the top of
.ts: the accent, the card, its border and its text. - Cards in view. Change the numbers after
--ts-show. Set all three to1for one large quote at a time. - Font. The slider sets none, so it uses your page's font.
- Photos or stars. Add an
imgor a line of stars inside afigure, above the quote. The cards grow to fit.
Add arrow buttons
A row that scrolls is enough on a phone. On a desktop, visitors look for arrows. This takes two pastes.
First, put the buttons inside the slider, just before its last </div>:
Arrow buttons: HTML
<div class="ts-buttons">
<button type="button" data-ts-move="-1" aria-label="Previous testimonial">←</button>
<button type="button" data-ts-move="1" aria-label="Next testimonial">→</button>
</div>Then paste this after the slider. The script moves the row by one card and works for every slider on the page.
Arrow buttons: CSS and JavaScript
<style>
.ts-buttons {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
.ts-buttons button {
width: 2.75rem;
height: 2.75rem;
border: 1.5px solid var(--ts-accent);
border-radius: 50%;
background: transparent;
color: var(--ts-accent);
font-size: 1.125rem;
cursor: pointer;
}
.ts-buttons button:hover {
background: var(--ts-accent);
color: #ffffff;
}
</style>
<script>
document.querySelectorAll(".ts").forEach(function (slider) {
var track = slider.querySelector(".ts-track");
slider.querySelectorAll("[data-ts-move]").forEach(function (button) {
button.addEventListener("click", function () {
var slide = track.querySelector(".ts-slide");
var gap = parseFloat(getComputedStyle(track).columnGap) || 0;
track.scrollBy({ left: (slide.offsetWidth + gap) * Number(button.dataset.tsMove) });
});
});
});
</script>Where to paste it
The code goes wherever your site accepts HTML: a Custom HTML block in WordPress, a code block in Squarespace, a Code Embed element in Webflow, or straight into the body of your own HTML page. Some site builders run scripts only on a paid plan, and each of those pages says what its platform asks for. The slider without buttons contains no script.
What hand-written code does not do
- Every change is an edit. A new testimonial means opening the page and changing the HTML, on each page the slider appears.
- Nothing is collected for you. You gather the quotes, the names and the permissions yourself.
- Reviews do not come in on their own. Reviews on Google or another site have to be typed in, and they do not update.
If you would rather keep your testimonials in one list and change them without touching the page, build the widget instead. A carousel is the same shape as this slider, with several cards in a row, and it can be published on ReTestimonial's free plan with up to 10 testimonials. A slider shows one long testimonial at a time. To show reviews from a review site, see the review widgets.