Testimonial Slider
A large one-at-a-time testimonial slider with avatars and prev/next buttons. React and Tailwind CSS, no carousel library.
TestimonialSlider.tsx
"use client";
// One large testimonial at a time, sliding sideways with prev/next buttons.
// Pure Tailwind + React, no carousel library. Works with Tailwind v3 and v4.
import { useState } from "react";
type Testimonial = { quote: string; name: string; role: string; avatar: string };
type Props = {
heading?: string;
testimonials?: Testimonial[];
};
const avatar = (id: string) => `https://images.unsplash.com/photo-${id}?w=160&h=160&q=80&auto=format&fit=crop&crop=faces`;
const DEFAULT_TESTIMONIALS: Testimonial[] = [
{ quote: "They found us a home in three weeks that we'd have spent a year looking for. Every viewing was worth the trip, and the paperwork just happened.", name: "Sarah Johnson", role: "Financial analyst", avatar: avatar("1573496359142-b8d87734a5a2") },
{ quote: "Clear advice, no pressure and a team that actually answered our calls. Selling our first home was far less stressful than we expected.", name: "David Okafor", role: "Architect", avatar: avatar("1568602471122-7832951cc4c5") },
{ quote: "From the first search to moving day, everything was organised for us. We recommend them to every friend who asks.", name: "Emma Lindqvist", role: "Product designer", avatar: avatar("1627161683077-e34782c24d81") },
];
const Chevron = ({ flip = false }: { flip?: boolean }) => (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75} strokeLinecap="round" aria-hidden className={`h-5 w-5 ${flip ? "rotate-180" : ""}`}>
<path d="m9 6 6 6-6 6" />
</svg>
);
export default function TestimonialSlider({ heading = "Happy customers", testimonials = DEFAULT_TESTIMONIALS }: Props) {
const [index, setIndex] = useState(0);
const count = testimonials.length;
const go = (step: number) => setIndex((i) => (i + step + count) % count);
return (
<section className="bg-[#f5f5f5] px-5 py-20 font-['Manrope',ui-sans-serif,system-ui,sans-serif] antialiased md:px-10 xl:py-28">
<div className="mx-auto max-w-7xl">
<div className="mb-10 flex items-end justify-between gap-6">
<h2 className="font-['Bricolage_Grotesque',ui-sans-serif,system-ui,sans-serif] text-[clamp(32px,4vw,50px)] font-semibold leading-[1.2] text-black">{heading}</h2>
<div className="flex gap-3">
<button type="button" onClick={() => go(-1)} aria-label="Previous testimonial" className="grid h-12 w-12 place-items-center rounded-full bg-white text-black transition-colors hover:bg-black hover:text-white">
<Chevron flip />
</button>
<button type="button" onClick={() => go(1)} aria-label="Next testimonial" className="grid h-12 w-12 place-items-center rounded-full bg-white text-black transition-colors hover:bg-black hover:text-white">
<Chevron />
</button>
</div>
</div>
<div className="overflow-hidden">
<div className="flex transition-transform duration-500 ease-out" style={{ transform: `translateX(-${index * 100}%)` }}>
{testimonials.map((t, i) => (
<figure key={t.name} aria-hidden={i !== index} className="w-full shrink-0">
<blockquote className="w-full text-2xl leading-snug text-black md:w-3/4 md:text-3xl">{t.quote}</blockquote>
<figcaption className="mt-10 flex items-center gap-4">
<img src={t.avatar} alt={t.name} className="h-[70px] w-[70px] rounded-full object-cover" />
<div>
<p className="text-lg font-semibold text-black">{t.name}</p>
<p className="text-[#575757]">{t.role}</p>
</div>
</figcaption>
</figure>
))}
</div>
</div>
</div>
</section>
);
}
How to use it
- Make sure your Next.js or React project has Tailwind CSS set up.
- Create
components/TestimonialSlider.tsxand paste the code. - Import it into a page and replace the placeholder text and images with your own.

Ready for a faster site that's easier to edit?
Book a free 30-minute call. We'll review your current site and send a fixed-price plan. No pressure, no pitch.
Book Free Call No commitment required


