Testimonial Carousel

Rotating display of testimonials.

html

<div class="carousel" data-active="1" style="--accent:#241430;">
  <button type="button" class="carousel__arrow carousel__arrow--prev" aria-label="Previous testimonial">&#8249;</button>
  <div class="carousel__track">
    <blockquote class="carousel__slide" data-slide="1">&ldquo;Cut our reporting time in half.&rdquo;<cite>&mdash; Priya, Northwind</cite></blockquote>
    <blockquote class="carousel__slide" data-slide="2">&ldquo;Support that actually responds fast.&rdquo;<cite>&mdash; Omar, Cobalt</cite></blockquote>
    <blockquote class="carousel__slide" data-slide="3">&ldquo;Rolled out to 40 people in a day.&rdquo;<cite>&mdash; Lena, Fenwick</cite></blockquote>
  </div>
  <button type="button" class="carousel__arrow carousel__arrow--next" aria-label="Next testimonial">&#8250;</button>
  <div class="carousel__dots" role="tablist" aria-label="Testimonials">
    <span class="carousel__dot" data-dot="1"></span>
    <span class="carousel__dot" data-dot="2"></span>
    <span class="carousel__dot" data-dot="3"></span>
  </div>
</div>
<style>
.carousel { font: 16px/1.5 system-ui, sans-serif; color: #241430; max-width: 360px; text-align: center; padding: 24px; position: relative; }
.carousel__slide { display: none; margin: 0 0 16px; font-size: 17px; font-weight: 500; }
.carousel__slide cite { display: block; margin-top: 10px; font-size: 13px; font-style: normal; color: #241430aa; }
.carousel[data-active="1"] .carousel__slide[data-slide="1"],
.carousel[data-active="2"] .carousel__slide[data-slide="2"],
.carousel[data-active="3"] .carousel__slide[data-slide="3"] { display: block; }
.carousel__arrow { position: absolute; top: 50%; transform: translateY(-50%); background: none; border: none; font-size: 26px; color: var(--accent); cursor: pointer; padding: 4px 8px; }
.carousel__arrow--prev { left: 0; }
.carousel__arrow--next { right: 0; }
.carousel__arrow:hover, .carousel__arrow:focus-visible { opacity: .7; }
.carousel__dots { display: flex; gap: 8px; justify-content: center; }
.carousel__dot { width: 8px; height: 8px; border-radius: 50%; background: #24143025; }
.carousel[data-active="1"] .carousel__dot[data-dot="1"],
.carousel[data-active="2"] .carousel__dot[data-dot="2"],
.carousel[data-active="3"] .carousel__dot[data-dot="3"] { background: var(--accent); }
</style>

tailwind

<div class="carousel-tw carousel-tw--1 relative font-sans text-[#241430] max-w-[360px] text-center p-6" style="--accent:#241430;">
  <button type="button" class="absolute top-1/2 left-0 -translate-y-1/2 bg-transparent border-0 text-2xl cursor-pointer px-2 hover:opacity-70" style="color:#241430;" aria-label="Previous testimonial">&#8249;</button>
  <div>
    <blockquote class="carousel-tw__slide hidden m-0 mb-4 text-[17px] font-medium" data-slide="1">&ldquo;Cut our reporting time in half.&rdquo;<cite class="block mt-2.5 text-[13px] not-italic text-[#241430]/65">&mdash; Priya, Northwind</cite></blockquote>
    <blockquote class="carousel-tw__slide hidden m-0 mb-4 text-[17px] font-medium" data-slide="2">&ldquo;Support that actually responds fast.&rdquo;<cite class="block mt-2.5 text-[13px] not-italic text-[#241430]/65">&mdash; Omar, Cobalt</cite></blockquote>
    <blockquote class="carousel-tw__slide hidden m-0 mb-4 text-[17px] font-medium" data-slide="3">&ldquo;Rolled out to 40 people in a day.&rdquo;<cite class="block mt-2.5 text-[13px] not-italic text-[#241430]/65">&mdash; Lena, Fenwick</cite></blockquote>
  </div>
  <button type="button" class="absolute top-1/2 right-0 -translate-y-1/2 bg-transparent border-0 text-2xl cursor-pointer px-2 hover:opacity-70" style="color:#241430;" aria-label="Next testimonial">&#8250;</button>
  <div class="flex gap-2 justify-center" role="tablist" aria-label="Testimonials">
    <span class="carousel-tw__dot w-2 h-2 rounded-full bg-[#24143025]" data-dot="1"></span>
    <span class="carousel-tw__dot w-2 h-2 rounded-full bg-[#24143025]" data-dot="2"></span>
    <span class="carousel-tw__dot w-2 h-2 rounded-full bg-[#24143025]" data-dot="3"></span>
  </div>
</div>
<style>
.carousel-tw--1 .carousel-tw__slide[data-slide="1"], .carousel-tw--2 .carousel-tw__slide[data-slide="2"], .carousel-tw--3 .carousel-tw__slide[data-slide="3"] { display: block; }
.carousel-tw--1 .carousel-tw__dot[data-dot="1"], .carousel-tw--2 .carousel-tw__dot[data-dot="2"], .carousel-tw--3 .carousel-tw__dot[data-dot="3"] { background: var(--accent); }
</style>

react

import { useState } from "react";

export function TestimonialCarousel({
  activeSlide = "1",
  accent = "#241430",
  showArrows = true,
}) {
  const slides = [
    { quote: "Cut our reporting time in half.", cite: "Priya, Northwind" },
    { quote: "Support that actually responds fast.", cite: "Omar, Cobalt" },
    { quote: "Rolled out to 40 people in a day.", cite: "Lena, Fenwick" },
  ];
  const [index, setIndex] = useState(Number(activeSlide) - 1);
  const move = (delta) => setIndex((i) => (i + delta + slides.length) % slides.length);
  return (
    <div style={{ font: "16px/1.5 system-ui, sans-serif", color: "#241430", maxWidth: 360, textAlign: "center",
      padding: 24, position: "relative" }}>
      {showArrows && (
        <button type="button" onClick={() => move(-1)} aria-label="Previous testimonial" style={{ position: "absolute",
          top: "50%", left: 0, transform: "translateY(-50%)", background: "none", border: "none", fontSize: 26,
          color: accent, cursor: "pointer", padding: "4px 8px" }}>&#8249;</button>
      )}
      <blockquote style={{ margin: "0 0 16px", fontSize: 17, fontWeight: 500 }}>
        &ldquo;{slides[index].quote}&rdquo;
        <cite style={{ display: "block", marginTop: 10, fontSize: 13, fontStyle: "normal", color: "#241430aa" }}>&mdash; {slides[index].cite}</cite>
      </blockquote>
      {showArrows && (
        <button type="button" onClick={() => move(1)} aria-label="Next testimonial" style={{ position: "absolute",
          top: "50%", right: 0, transform: "translateY(-50%)", background: "none", border: "none", fontSize: 26,
          color: accent, cursor: "pointer", padding: "4px 8px" }}>&#8250;</button>
      )}
      <div role="tablist" aria-label="Testimonials" style={{ display: "flex", gap: 8, justifyContent: "center" }}>
        {slides.map((_, i) => (
          <span key={i} style={{ width: 8, height: 8, borderRadius: "50%", background: i === index ? accent : "#24143025" }} />
        ))}
      </div>
    </div>
  );
}