Skip to content
Template Empire
Browse components

Testimonial Marquee

A free testimonial section whose two rows scroll past in opposite directions.

A tenant used to wait nineteen days for a repair, and most of that was the surveyor’s photographs never reaching the contractor. We are at six days now, and most of the six is waiting on parts.
Bernadette QuinnHead of Repairs, Ravenscourt Homes
First winter in eleven years we have not had to run a no-access clinic.
Gary MullanRepairs Manager, Halewood Housing Trust
The asbestos register is attached to the job before an operative leaves the depot. That single change closed more of our safety incidents than anything else we bought last year.
Sadia RahmanCompliance Lead, Corn Exchange Group
We moved 4,200 homes onto Jobsheet over one weekend and kept the old system running alongside it in case. We switched that off after nine days.
Iain ProsserDirector of Assets, Brookmill Housing
Our contractors took to it faster than our own staff did.
Nia FletcherProgramme Manager, Ardleigh Homes
Complaints about repairs fell by a third in two quarters. The repairs themselves barely got faster in that time. Tenants simply stopped being told one thing and seeing another.
Owen PritchardCustomer Services Lead, Windlesham Homes

Testimonial Marquee is a free testimonial section for Next.js and React, styled with Tailwind, that scrolls two rows of quote cards past each other in opposite directions. It is the testimonial marquee react pattern driven entirely by CSS: one transform keyframe per row, no animation library, no timers.

Hovering either row pauses it in place, and a pause checkbox above the rows stops both, so a keyboard-only reader can freeze the section without a pointer and nothing scrolls away mid-sentence. That control is CSS driven, so it works before hydration and with JavaScript disabled. Each testimonial is a real figure with the quote in a blockquote and the attribution in a figcaption, and the duplicate copy that makes the loop seamless is hidden from assistive technology, so every quote is announced exactly once.

Reduced motion leaves both rows static and horizontally scrollable, and each row is reachable by keyboard so every card stays available. JavaScript only refines the CSS loop: it pauses both rows while the section is offscreen or the tab is in the background, and sizes each copy to the row it occupies so the track never exposes a gap.

Install

Add the @te registry to your components.json once:

{
  "registries": {
    "@te": "https://templateempire.io/r/{name}.json"
  }
}

Then install the component:

npx shadcn@latest add @te/quote-marquee

Usage

import { QuoteMarquee } from "@/components/quote-marquee";

export default function Page() {
  return (
    <QuoteMarquee
      items={[
        {
          quote: "We shipped the pricing page in an afternoon.",
          name: "Priya Raman",
          role: "Head of Product, Doubleline",
        },
        {
          quote: "The motion reads as one system, not twelve opinions.",
          name: "Marcus Bell",
          role: "Design Lead, Fieldnote",
        },
        {
          quote: "Our engineers stopped rebuilding the same section every quarter.",
          name: "Ada Okonjo",
          role: "VP Engineering, Northwind",
        },
        {
          quote: "It shipped accessible by default, which never happens.",
          name: "Tomas Lindqvist",
          role: "Principal Designer, Kite",
        },
      ]}
    />
  );
}

Props

PropTypeDefaultDescription
items{ quote: string; name: string; role: string }[]requiredFour to twelve testimonials, split evenly across the two rows in the order supplied.
classNamestringundefinedAdditional classes merged onto the section root.

Customisation

Testimonial Marquee exposes three Tier 3 CSS custom properties. Set --quote-dur for the first row's loop period, --quote-gap for the space between cards, and --quote-card for the card fill. The second row runs at four thirds of --quote-dur, so the two rows drift apart and only return to the same phase every four first-row cycles, three minutes at the default, rather than beating together on every one; there is no separate speed prop for it by design. --quote-card falls back to the shadcn --card token before this site's --surface, so an install gets its own panel colour rather than a transparent card. All three are read with their defaults inline, so an override on :root or on any wrapper reaches the component, and the timing tokens are mapped onto the underlying text-marquee's own tokens for you.

.landing-testimonials {
  --quote-dur: 60s;
  --quote-gap: 2rem;
  --quote-card: var(--muted);
}

Frequently asked questions

Is Testimonial Marquee free for commercial use?
Yes. Testimonial Marquee is MIT licensed, so you can use it in personal and commercial projects without attribution.
Is Testimonial Marquee accessible and performant?
Yes. Each testimonial is a real figure with the quote in a blockquote and the attribution in a figcaption, and the duplicate copy that makes the loop seamless is aria-hidden, so assistive technology encounters every quote exactly once. Hovering a row pauses it in place, and a keyboard-reachable pause checkbox stops both rows, so nothing scrolls away mid-sentence; it is CSS driven, so it works with JavaScript disabled as well. Reduced motion leaves both rows static and horizontally scrollable, and each row carries a tab stop and an accessible name so a keyboard-only reader can scroll it. Each row animates one transform keyframe in CSS with no animation library and no timers, and both rows pause while the section is offscreen or the tab is in the background.
How do I change the speed or the spacing?
Set --quote-dur and --quote-gap on any ancestor. --quote-dur is the first row's loop period and defaults to 45s; a larger value is slower. The second row is derived from it at four thirds the period, which is what keeps the two rows out of phase for all but one cycle in four, so it deliberately has no token of its own. --quote-gap defaults to 1.5rem and sets the space between cards.

Related components

Want the full page this belongs to?Template Empire builds complete Next.js templates with social proof sections and this motion system throughout. Browse the paid templates that pair with these free components.Explore Template Empire templates