DHxWhy

Highlight Testimonial Section

Testimonials 26

About

Highlight testimonial section for React and Tailwind CSS (shadcn/ui) that sweeps a highlight across the key phrase of each customer quote and lifts that phrase into the section headline. It is built to let visitors read why customers are happy in one line before they read the full quote. A testimonial section is a social-proof block that displays customer quotes with names and roles; this one takes testimonials with a quote, a highlight phrase, an author, a role and an optional avatar, wraps the phrase in a mark element, animates the highlight with a background-size sweep and promotes it into the headline with a FLIP translate and scale animation using the Web Animations API. Supports controlled and uncontrolled index, an optional auto-rotate interval that pauses on hover and focus, dot and arrow navigation with arrow keys, carousel ARIA semantics with a polite live headline, light and dark highlight tokens from the shadcn chart palette, and a reduced motion fallback. Use it on landing pages, pricing pages, case studies and onboarding screens. The included demo rotates three sample quotes and makes no requests. Requires React and lucide-react, with no animation library.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Highlight Testimonial Section to your project and fits it to your theme.

Code

How Highlight Testimonial Section is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import * as React from "react";
import { RotateCcw } from "lucide-react";
import { HighlightTestimonialSection } from "@/components/ui/highlight-testimonial-section";
import type { Testimonial } from "@/components/ui/highlight-testimonial-section";

type DemoProps = { autoplay?: boolean; bare?: boolean; index?: number; interval?: number };

const TESTIMONIALS: Testimonial[] = [
  { quote: "We moved three teams over in a week. Onboarding took minutes, not days, and nobody asked for the old tool back.", highlight: "Onboarding took minutes, not days", author: "Maria Lopez", role: "Head of Platform, Acme", avatar: "https://cdn.21st.dev/assets/mirror/1b/1bd1e03a7e1a1548cdab4819240bd550a5daab2767344e0e302833defe91f142.jpg" },
  { quote: "The dashboards finally answer the questions our execs ask. Reports that used to take an afternoon now take a click.", highlight: "Reports that used to take an afternoon now take a click", author: "Jon Park", role: "Data lead, Northwind", avatar: "https://cdn.21st.dev/assets/mirror/d2/d2996fcdda8bb515128ec84aa81468b77bbb31c13b7de0847dc95a5e5b34a90f.jpg" },
  { quote: "Support replied in under ten minutes with an actual fix, twice. That is the main reason we renewed.", highlight: "replied in under ten minutes with an actual fix", author: "Aisha Rahman", role: "CTO, Fernwood", avatar: "https://cdn.21st.dev/assets/mirror/cc/cc21fdbbb78a68b33c08747bc6566e1f1d28f604f191fa5a6ea344e0f7ece19e.jpg" },
];

export default function HighlightTestimonialSectionDemo({ autoplay = true, bare = false, index: preset, interval = 2500 }: DemoProps) {
  const [index, setIndex] = React.useState(preset ?? 0);
  const [run, setRun] = React.useState(0);
  const [motion, setMotion] = React.useState({ ready: false, reduced: true });

  React.useEffect(() => {
    const media = window.matchMedia("(prefers-reduced-motion: reduce)");
    const update = () => setMotion({ ready: true, reduced: media.matches });
    update();
    media.addEventListener("change", update);
    return () => media.removeEventListener("change", update);
  }, []);

  return (
    <main className="flex min-h-svh w-full flex-col items-center justify-center gap-5 bg-background px-5 py-6 text-foreground">
      {!bare && (
        <header className="w-full max-w-[460px]">
          <p className="mb-1 text-[10px] font-medium uppercase tracking-[0.2em] text-muted-foreground">Social proof, one line at a time</p>
          <h1 className="text-[24px] font-semibold leading-tight tracking-tight">The best phrase becomes the headline.</h1>
        </header>
      )}
      <div className="w-full max-w-[460px]">
        <HighlightTestimonialSection key={run} testimonials={TESTIMONIALS} index={index} onIndexChange={setIndex} interval={autoplay && motion.ready && !motion.reduced ? interval : 0} />
      </div>
      {!bare && (
        <footer className="flex w-full max-w-[460px] items-center justify-between text-[10px] text-muted-foreground">
          <span>Sample quotes · hover to pause, arrows to browse</span>
          <button type="button" data-replay onClick={() => { setIndex(0); setRun((previous) => previous + 1); }} className="inline-flex items-center gap-1 rounded px-1 underline decoration-border underline-offset-4 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"><RotateCcw className="size-3" aria-hidden="true" />Replay</button>
        </footer>
      )}
    </main>
  );
}

Dependencies

More by DHxWhy

View all
  • Product Rail Gallery – Fashion Store Hero Section

    Product Rail Gallery – Fashion Store Hero Section

    1.1K
  • Sun Moon Dark Mode Theme Toggle

    Sun Moon Dark Mode Theme Toggle

    256
  • Live Notification Feed

    Live Notification Feed

    143
  • Country Flags Globe

    Country Flags Globe

    128
  • Airplane Window Gallery

    Airplane Window Gallery

    117
  • QR Code Scan Login Form with Password Fallback

    QR Code Scan Login Form with Password Fallback

    110
  • Domino Scroll Image Gallery

    Domino Scroll Image Gallery

    109
  • Ferris Wheel Image Gallery

    Ferris Wheel Image Gallery

    102

More in Testimonials

View all
  • Testimonial

    Testimonial

    121
  • Staggered Testimonials Grid

    Staggered Testimonials Grid

    150
  • testimonial

    testimonial

    827
  • Testimonials Section

    Testimonials Section

    159
  • Testimonials

    Testimonials

    247
  • Testimonial 2

    Testimonial 2

    33
  • Card Studio

    Card Studio

    15
  • Testimonial Card

    Testimonial Card

    71