Page Sections

Review Ticker

A React single-line, endlessly scrolling ticker of short testimonials separated by stars and fading at both edges, with an optional reverse second line, pausing on hover or focus and a visible pause button.

A social-proof band for under the hero. Each testimonial is just a small avatar, one short line and "— name, company", separated by accent-coloured stars and fading out at both edges. The content is duplicated and scrolled endlessly with a linear CSS animation; speed is given in px/s and converted into a period once the content width is measured. With rows={2} the second row scrolls the other way, offset by half. It pauses automatically on mouse hover, keyboard focus, when the tab is hidden or when the band leaves the viewport, and a visible pause button sits on the right.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add review-ticker.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

50
0
Active edited usageDefault props
<ReviewTicker/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/review-ticker.en.json

Or, once registries are configured in components.json, install it as @webberui/review-ticker.

Usage

import { ReviewTicker } from "@/components/ui/review-ticker";

<ReviewTicker
  rows={2}
  speed={50}
  testimonials={[
    { quote: "門市排班從半天縮到十分鐘,店長終於能準時下班。", name: "林予安", company: "青嶼咖啡" },
    { quote: "出貨異常的通知時間縮短了一半,客服再也不用一通通電話追司機。", name: "陳柏宏", company: "北辰物流", short: "出貨異常通知時間縮短一半" },
    { quote: "結帳流程更短,這一季回購率是開站以來最高。", name: "黃冠廷", company: "好日選物" },
  ]}
/>

Props

PropTypeDefaultDescription
testimonialsReviewTickerItem[]—Array of testimonials
eyebrowstring—Small label above the title; shown only when provided
titlestring—Section title; hidden by default, placed above the ticker when provided
subtitlestring—Supporting text below the title
rows1 | 21Number of rows; with 2 the second row scrolls the other way, offset by half
speednumber50Scroll speed (px/s), converted into a period after measuring the content width
direction"left" | "right""left"Scroll direction of the first row
borderedbooleantrueWhether to show thin top and bottom borders
durationnumber—Fixed period (seconds); overrides the value derived from speed
maxQuoteLengthnumber40Quotes longer than this many characters are truncated with … (not applied when short is set)
pausedboolean—Controlled: whether the user has paused the ticker
defaultPausedbooleanfalseUncontrolled: whether the ticker starts paused
onPausedChange(paused: boolean) => void—Called when the user presses the pause button
regionLabelstring"Customer testimonials"Accessible label for the section when no title is provided
pauseLabelstring"Pause scrolling"Accessible label of the pause button
classNamestring—Merged into the root section

ReviewTickerItem

FieldTypeDefaultDescription
quotestring—Quote text; when truncated, hovering shows the full text
namestring—Reviewer name
companystring—Company name, shown after the name
rolestring—Job title; shown instead when company is not provided
shortstring—Short version shown on the ticker; quote is truncated automatically when omitted
avatarHuenumber—Avatar gradient hue (0–360); derived from the index by the golden angle when omitted
avatarSrcstring—Avatar image URL; shows an initials gradient circle when omitted
highlightstring—Fragment of the displayed text to mark in the accent colour
ratingnumber—Not displayed here; kept so data can be shared with other testimonial components
metric{ value: string; label: string }—Not displayed here; kept so data can be shared with other testimonial components

How it works

  • Timing variables: the period reads --wb-review-ticker-duration (derived from speed by default, 45s before measurement), the entrance fade reads --wb-review-ticker-enter-duration (600ms), and the accent reads --wb-review-ticker-accent (default #0d9488, #2dd4bf in dark mode). Setting --wb-review-ticker-duration globally overrides speed.
  • Seamless loop: the content is duplicated and translateX runs from 0 to -50%. With only a few testimonials, when one set is narrower than the viewport, the set is repeated until it fills the width before being duplicated, so no gap shows.
  • Truncation counts characters for both Chinese and Latin text, so no character is ever cut in half. Hovering a truncated quote shows the full text above the band, and screen readers read the full quote directly.
  • No photos needed: avatars use initials (last two characters for Chinese names, first letters for Latin names) on a two-tone gradient whose hue follows the golden angle by index, generated deterministically; pass avatarSrc for a real image.
  • The background is a very faint wave line SVG built from a few summed sines, producing identical output on server and client.

Accessibility

  • The root is a section; with a title it uses aria-labelledby, otherwise aria-label="Customer testimonials"
  • The duplicated loop content and the second row (a repeat of the same testimonials) are aria-hidden, so screen readers read each testimonial once
  • Each testimonial is a figure / blockquote / figcaption; when truncated, the visible text is hidden from assistive tech and the full quote is read instead
  • A visible pause button sits on the right of the band (aria-pressed conveys the state), meeting WCAG 2.2.2; the ticker also pauses on hover, keyboard focus, a hidden tab or when scrolled out of view
  • When the user has enabled reduce motion, nothing scrolls: the ticker becomes a single static row that can be focused and scrolled horizontally with the keyboard, the entrance fade shortens to 150ms, and the pause button is hidden