Source Code

<section class="rts-hero">
  <div class="rts-copy">
    <span class="rts-eyebrow">Trusted by product teams everywhere</span>
    <h1 class="rts-h1">The design tool teams<br>actually stick with</h1>
    <p class="rts-sub">Real-time collaboration, versioning that never loses work, and a component system that scales with your team.</p>
    <a href="#" class="rts-cta">Get started free</a>
  </div>

  <div class="rts-spotlight" id="rtsSpotlight">
    <div class="rts-slide active" data-index="0">
      <p class="rts-quote">"We replaced three separate tools with this. Our design-to-dev handoff time dropped by half in the first month."</p>
      <div class="rts-person"><span class="rts-avatar" style="background:linear-gradient(135deg,#f472b6,#ec4899)">MK</span><div><strong>Maya Kessler</strong><span>Head of Design, Fluent</span></div></div>
    </div>
    <div class="rts-slide" data-index="1">
      <p class="rts-quote">"The versioning alone justified the switch. I have never once lost work or fought a merge conflict since we moved."</p>
      <div class="rts-person"><span class="rts-avatar" style="background:linear-gradient(135deg,#60a5fa,#6366f1)">DP</span><div><strong>Daniel Petrov</strong><span>Product Designer, Northwind</span></div></div>
    </div>
    <div class="rts-slide" data-index="2">
      <p class="rts-quote">"Our component library finally stays in sync across every file. Onboarding a new designer now takes a day, not a week."</p>
      <div class="rts-person"><span class="rts-avatar" style="background:linear-gradient(135deg,#34d399,#10b981)">AR</span><div><strong>Aisha Rahman</strong><span>Design Lead, Coral</span></div></div>
    </div>

    <div class="rts-dots" id="rtsDots">
      <button type="button" class="rts-dot active" data-goto="0" aria-label="Testimonial 1"></button>
      <button type="button" class="rts-dot" data-goto="1" aria-label="Testimonial 2"></button>
      <button type="button" class="rts-dot" data-goto="2" aria-label="Testimonial 3"></button>
    </div>
  </div>
</section>

Hero with Rotating Testimonial Spotlight — Free Snippet

Hero with Rotating Testimonial Spotlight · Heroes · Plain HTML, CSS & JS · Live preview

What's included

Features

Auto-rotating testimonial carousel with real setInterval-driven advancement
Single goToSlide() function shared by both auto-advance and manual dot clicks
Cross-fade + subtle vertical shift transition, not an abrupt swap
Absolutely-positioned inactive slides keep the card height matched to the active quote
Pauses on both mouse hover and keyboard focus — not mouse-only
Manual dot navigation resets the auto-advance clock for a full reading interval
Growing pill-shaped active dot indicates carousel position at a glance
Modulo-wrapped index math keeps navigation correct at either end of the list
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons

About this UI Snippet

Hero with Rotating Testimonial Spotlight — Auto-Rotating Quote Carousel That Pauses on Hover

Screenshot of the Hero with Rotating Testimonial Spotlight snippet rendered live

Social proof works best when it sits right beside the claim it's backing up — not three scrolls down the page in a dedicated testimonials section a visitor may never reach. This hero puts a real, rotating customer quote directly next to the headline, cycling through several testimonials automatically while still giving the visitor manual control over which one they're reading.

One `goToSlide()` function, two entry points

Both the automatic timer and the clickable dots call the exact same goToSlide(index) function rather than duplicating the slide-swap logic in two places. It removes .active from the current slide and dot, computes the new index with (index + slides.length) % slides.length (which correctly wraps a negative or out-of-range index back into bounds), and adds .active to the new slide and dot. Because there is only one function that actually changes which slide is showing, a bug fix or visual tweak to the transition only needs to happen in one place.

The cross-fade, not a slide-in

The default (inactive) .rts-slide state is opacity: 0, transform: translateY(8px), and position: absolute so inactive slides stack on top of each other without affecting layout height. The .active class resets opacity and transform and switches back to normal document flow (position: relative) so the spotlight card's height matches whichever quote is currently showing, rather than being pinned to the tallest quote in the set. This means shorter and longer quotes each get a naturally-sized card instead of extra empty space.

Pause on hover and keyboard focus

mouseenter/mouseleave call stopTimer()/startTimer() so a visitor reading a testimonial doesn't have it yanked away mid-read by the auto-advance. Just as important, focusin/focusout do the same thing for keyboard users — a testimonial carousel that only pauses on mouse hover but keeps auto-advancing while a keyboard user is tabbed into one of the dot buttons is a common accessibility gap that this snippet avoids by handling both input modes identically.

Manual navigation resets the clock

Clicking a dot calls goToSlide() directly and then calls startTimer() again, which internally calls stopTimer() first to clear any existing interval before setting a fresh one. This means a visitor who manually picks the third testimonial gets a full 4.5 seconds to read it before auto-advance resumes, rather than the auto-advance firing a moment later because the old timer was still running on its original schedule.

Growing pill dots for the active state

Rather than three plain equal-size dots, the .active dot animates its own width from 7px to 22px with border-radius: 4px, turning it into a short pill while the inactive dots stay circular. This is a small but effective way to make the current position unambiguous at a glance, and the transition on width and background makes the change itself readable rather than an instant jump.

Adding a fourth testimonial

Copy an existing .rts-slide block with a new data-index, quote, and .rts-person block, and add a matching .rts-dot button with the next data-goto value. The JavaScript re-queries .rts-slide and .rts-dot via querySelectorAll on load, and slides.length inside goToSlide()'s modulo wrap means the carousel automatically accounts for however many slides exist — no other JavaScript changes are required.

Slowing down or speeding up the rotation

Change the INTERVAL constant (currently 4500 milliseconds) to any value. A slower rotation reads better for longer quotes; a faster one suits shorter, punchier testimonials.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Rather than debugging carousel timing issues by trial and error, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain why goToSlide() is the single shared entry point for both the automatic timer and the manual dot clicks, and what would go wrong — duplicated logic, or the two paths falling out of sync — if the dot click handler manipulated the active classes directly instead of calling the same function. The same assistant is useful for extending the pattern: ask it to add swipe-gesture support for mobile, add previous/next arrow buttons using the existing modulo-wrapped goToSlide(current - 1) / goToSlide(current + 1) calls, or convert the vanilla setInterval-based carousel into a React version using useEffect cleanup to correctly clear the interval on unmount. It can also help you audit the accessibility of the pause-on-focus behavior against your specific testimonial content and dot button labeling. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a hero section in plain HTML, CSS, and vanilla JavaScript with a headline and CTA on one side and an auto-rotating testimonial spotlight card on the other — three testimonials cross-fading automatically, with clickable dot navigation, no carousel library.

Requirements:
- Three testimonial slides (quote text, avatar initials, name, and role), stacked on top of each other using absolute positioning so inactive slides don't affect the card's layout, with the active slide switching to normal document flow so the card's height matches whichever quote is currently showing.
- The active slide should cross-fade in (opacity and a small vertical transform, both CSS-transitioned) rather than appearing or disappearing instantly.
- A single shared function that changes which slide is active — used both by an automatic setInterval-driven advance every 4-5 seconds and by clicking one of three dot buttons below the card, so there is exactly one place in the code that manipulates which slide has the active class. Use modulo arithmetic on the slide index so the function safely wraps around at either end of the list.
- Auto-advance must pause while the mouse is hovering the card AND while any dot button has keyboard focus (not mouse-hover only), and resume when the mouse leaves or focus moves elsewhere.
- Clicking a dot to manually jump to a testimonial should reset the auto-advance timer so the visitor gets a full interval to read the selected testimonial before it changes again.
- The active dot should visually grow into a short pill shape distinct from the other circular dots, with a smooth transition.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Watch it auto-rotateThe spotlight card cross-fades to the next testimonial every 4.5 seconds automatically.
  2. 2
    Hover or focus the cardAuto-advance pauses while the mouse is over the card or a dot has keyboard focus, and resumes when it leaves.
  3. 3
    Click a dot to jumpManually selecting a testimonial resets the auto-advance timer so you get a full interval to read it.
  4. 4
    Edit the testimonialsChange the quote text, name, role, and avatar initials/color inside each .rts-slide block.
  5. 5
    Add or remove testimonialsCopy or delete a .rts-slide plus its matching .rts-dot — the carousel adapts to however many slides exist.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

SaaS heroes needing above-the-fold social proof
Puts real customer language directly beside the value proposition, rather than several scrolls away.
Agencies and consultancies
Rotate client outcome quotes to reinforce credibility on the very first screen a prospect sees.
Design and creative tool landing pages
Pair with the Hero with Animated Stat Counters below for both quantitative and qualitative proof.
Learn accessible carousel pause handling
Study why the snippet pauses on focusin/focusout in addition to mouseenter/mouseleave for keyboard users.
Marketplace and services platforms
Rotate testimonials from different customer segments (buyers, sellers, enterprise) in the same spotlight slot.
Related: Testimonial Slider
See the standalone Testimonial Slider for a non-hero, dedicated testimonials-section version of this carousel pattern.

Got questions?

Frequently Asked Questions

The dot click handler calls goToSlide() to jump directly to that testimonial, then calls startTimer() again. startTimer() internally calls stopTimer() first to clear the existing interval before creating a new one, so the visitor gets a full 4.5-second interval starting from their manual click rather than the auto-advance firing on the old schedule a moment later.

All slides are stacked with position: absolute inside a relatively-positioned card, and the .active slide switches to position: relative so the card's height matches its content. This lets the opacity and transform CSS transitions animate smoothly between two overlapping slides during the cross-fade; using display: none on the outgoing slide would remove it from the flow instantly with no way to fade it out.

Both. In addition to mouseenter/mouseleave, the code listens for focusin/focusout on the spotlight container, which fires when a dot button inside it gains or loses keyboard focus. This means a keyboard user tabbing into the dots gets the same pause behavior as a mouse user hovering the card.

Copy an existing .rts-slide <div> (updating its data-index, quote text, and .rts-person details) and add a matching .rts-dot button with the next data-goto value. Because slides and dots are queried fresh via querySelectorAll and the wrap math uses slides.length, no other JavaScript needs to change.

The expression (index + slides.length) % slides.length wraps any index — including one that would be negative or past the end of the array — back into the valid [0, slides.length) range. This is what would let you add "previous" and "next" arrow buttons calling goToSlide(current - 1) or goToSlide(current + 1) safely at either end of the list without extra bounds-checking code.

Change the INTERVAL constant near the top of the JS panel, given in milliseconds (the default is 4500, i.e. 4.5 seconds). A larger value gives visitors more time per testimonial; a smaller one cycles through them faster.