One-Line Quote Carousel
A minimal React carousel showing a single large-type sentence; quotes cross-fade vertically with a slight blur, leaving only the author and fine dot indicators below over slowly flowing hairlines.
A testimonial block for pages with lots of white space: no card frame and no avatar — just one large centred quote, a single author line and a row of fine dots. On each switch the old sentence drifts up, fades and blurs slightly while the new one rises in and comes into focus, the two overlapping as they pass; the author line follows 120ms later. Behind it, a few hairline curves generated from seed flow slowly on a 20-second cycle. The quote area is fixed to the height of the longest quote, so the layout never jumps between switches.
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 one-line-quote-carousel.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<OneLineQuoteCarousel/>
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/one-line-quote-carousel.en.jsonOr, once registries are configured in components.json, install it as @webberui/one-line-quote-carousel.
Usage
import { OneLineQuoteCarousel } from "@/components/ui/one-line-quote-carousel";
<OneLineQuoteCarousel
testimonials={[
{
quote: "Since the redesign, our weekend waitlist is half as long.",
name: "Maya Lin",
role: "Head of Operations",
company: "Islet Coffee",
highlight: "half as long",
},
{
quote: "New drivers run a full route on their own from day one.",
name: "Owen Chen",
company: "Northstar Freight",
},
]}
interval={5000}
seed={9}
onIndexChange={(i) => console.log(`Switched to ${i + 1}`)}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
testimonials | OneLineQuoteCarouselItem[] | — | List of testimonials |
eyebrow | string | — | Small label above the title |
title | string | — | Section title; not shown by default, and when provided it becomes the section's aria-labelledby |
subtitle | string | — | Supporting text below the title |
autoplay | boolean | true | Whether to autoplay; always off when the user prefers reduced motion |
interval | number | 5000 | Autoplay interval (ms); reads --wb-one-line-quote-carousel-interval when omitted |
duration | number | 600 | Duration (ms) of the quote cross-fade; reads --wb-one-line-quote-carousel-duration when omitted |
index | number | — | Controlled current index; managed internally when omitted |
defaultIndex | number | 0 | Initial index in uncontrolled mode |
onIndexChange | (index: number) => void | — | Called when the index changes |
showBackdrop | boolean | true | Whether to show the flowing hairline backdrop |
seed | number | 9 | Random seed for the backdrop; the same seed always draws the same 3–5 curves |
className | string | — | Forwarded to the root section |
OneLineQuoteCarouselItem
| Field | Type | Default | Description |
|---|---|---|---|
quote | string | — | Quote text; one sentence works best |
name | string | — | Name |
role | string | — | Job title, appended after the name with " · " |
company | string | — | Company |
highlight | string | — | Fragment of the quote to mark in the accent colour |
rating | number | — | Rating (0–5); not shown here, kept so data can be shared with other testimonial components |
avatarHue | number | — | Hue; this component shows no avatar, kept so data can be shared |
metric | { value: string; label: string } | — | Key figure; not shown here, kept so data can be shared |
How it works
- Timing variables:
--wb-one-line-quote-carousel-interval(default5000ms) and--wb-one-line-quote-carousel-duration(default600ms). Passinginterval/durationwrites them into these variables, and the component reads them back from the computed style after mounting, so overriding the variables from an outer scope works too. The easing is fixed atcubic-bezier(0.22,1,0.36,1). - The accent colour only comes from
--wb-one-line-quote-carousel-accent(light default#0d9488, dark default#2dd4bf), used for the highlight, the eyebrow and the active dot. - The quote area stacks every quote invisibly in one grid cell as a "ruler", so its height always matches the longest quote; the current quote is absolutely positioned on top, and both old and new sentences exist during the cross-fade.
- The backdrop curves come from a deterministic PRNG (mulberry32) seeded by
seed, so server and browser draw identical paths; strokes stay 1px (vector-effect: non-scaling-stroke) and both ends fade out through a mask. - Autoplay pauses on hover, when keyboard focus is inside the carousel, when the pause button is pressed and when the tab goes to the background; every switch or resume restarts a full interval.
Accessibility
- The root element is a
section, labelled by the title viaaria-labelledbywhen there is one and byaria-labelotherwise; the carousel area isrole="region"witharia-roledescription="carousel", is focusable, and the ← / → keys switch quotes while it has focus - Quotes are marked up with
figure,blockquoteandfigcaption; the invisible ruler isaria-hiddenso nothing is read twice aria-live="off"while autoplay runs so screen readers are not interrupted on a timer; it switches topolitewhen paused or switched manually- Each dot is a
type="button"with anaria-labelsuch as "Testimonial 2: name", and the active one carriesaria-current - With autoplay on there is always a visible pause button at the end of the dot row (WCAG 2.2.2), with
aria-pressed; hovering or focusing also pauses - When the system "Reduce motion" setting is on: autoplay is off, the hairlines stop flowing, quotes no longer move or blur and fade in over 150ms instead, and the dots still switch quotes
Typed Quote Row
A React row of testimonial cards that take turns typing out their quotes character by character with a blinking caret; after a pause the next card begins, while the others show their full text dimmed.
Bento Case Studies
An uneven bento testimonial mosaic for React mixing one large featured case, metric tiles, short-quote tiles and partner wordmark tiles; tiles appear in grid order, numbers count up, and a read-the-case link appears on hover.