Page Sections

Quote & Result Columns

Static testimonial columns React component, each with a key quote and a results figure; on entry the dividers draw down, the figures count up from zero and the columns appear in sequence, under an optional generative contour backdrop.

Customer testimonials as "one complete case summary per column": each column opens with a large results figure (such as +38%) and its label, followed by a short rule, the quote and an author row. No scrolling, no carousel, no switching — the content sits side by side and the numbers lead. On entry the dividers draw from the top down and the columns appear in sequence, and each figure counts up from 0 once its column has appeared; an optional faint contour backdrop, generated from seed, draws once across the top of the section. Wide screens show 2, 3 or 4 columns; narrow screens stack automatically and switch to horizontal dividers.

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 quote-result-columns.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.

2
120
600
Active edited usageDefault props
<QuoteResultColumns/>

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/quote-result-columns.en.json

Or, once registries are configured in components.json, install it as @webberui/quote-result-columns.

Usage

import { QuoteResultColumns } from "@/components/ui/quote-result-columns";

<QuoteResultColumns
  eyebrow="Results"
  title="Let the numbers speak"
  columns={3}
  testimonials={[
    {
      quote: "Once checkout flowed smoothly, far fewer carts were abandoned.",
      highlight: "checkout flowed smoothly",
      name: "Maya Lin",
      role: "Head of Ecommerce",
      company: "Islet Coffee",
      metric: { value: "+38%", label: "Order conversion" },
    },
    {
      quote: "Drivers see reassignments on the road, and steady on-time rates brought us new routes.",
      name: "Ethan Chen",
      role: "Operations Manager",
      company: "Polaris Freight",
      metric: { value: "-27%", label: "Average delivery time" },
    },
    {
      quote: "Tutors step in before learners get stuck, and re-enrolment fills earlier.",
      name: "Grace Chang",
      role: "Director of Studies",
      company: "Lumen Academy",
      metric: { value: "2.4x", label: "Course completion" },
    },
  ]}
/>

Props

PropTypeDefaultDescription
testimonialsQuoteResultColumnsItem[]—Testimonials, one per column; wraps onto new rows past the column count
eyebrowReactNode—Small label above the title
titleReactNode"Customer results"Section title, also the accessible name of the section (the Chinese build defaults to "客戶成效" (customer results))
subtitleReactNode—Supporting text below the title
columns2 | 3 | 43Columns on wide screens; 4 lays out as 2 columns at medium widths, and narrow screens always stack
seednumber2Seed for the contour backdrop; the same seed always draws the same curves
showBackdropbooleantrueWhether to show the generative contour backdrop at the top of the section
staggernumber—Delay between columns appearing (ms); falls back to --wb-quote-result-columns-stagger (default 120ms)
durationnumber—Entrance duration of each column (ms); falls back to --wb-quote-result-columns-duration (default 600ms)
classNamestring—Merged into the root section

QuoteResultColumnsItem

FieldTypeDefaultDescription
quotestring—Full quote text
namestring—Customer name; the avatar shows initials (last two characters for Chinese names, first letters for Latin names)
rolestring—Job title
companystring—Company name, joined to the role with " · "
ratingnumber—Rating (0–5); not rendered here, kept for the shared data shape
avatarHuenumber—Avatar gradient hue (0–360); defaults to a golden-angle (137.508°) step based on the index
highlightstring—Fragment of the quote to highlight; must match the quote exactly
metric{ value: string; label: string }—Results figure and label; strongly recommended — without it the column shows a large quote mark instead

CSS variables

VariableDefaultDescription
--wb-quote-result-columns-accent#0d9488 (dark #2dd4bf)Accent for the figures, eyebrow, quote mark and highlight tint
--wb-quote-result-columns-stagger120msDelay between columns appearing
--wb-quote-result-columns-duration600msEntrance duration of each column

How it works

  • The count-up parses the first number in metric.value along with its prefix and suffix: +38% becomes prefix +, 38, suffix %; 1,200 hrs keeps its thousands separator; 2.4x keeps one decimal place. Values without a number (such as "Fully live") are shown as-is with no count.
  • Counting starts only after the column has finished appearing and runs for 1200ms with easeOut; server rendering and the pre-entrance state output the final value, so search engines and no-JavaScript readers get the full figure.
  • The contour backdrop builds 6 smooth curves from seed with a deterministic pseudo-random generator — no Math.random — so SSR and client match; it draws once over 1600ms on entry and fades out at the bottom with a mask.
  • Column dividers are vertical on wide screens (drawn top-down over 600ms) and become horizontal when stacked; with columns set to 4, medium widths use a 2×2 layout and the dividers move to match.
  • The entrance plays once (useInView once, 20% visible); remount with a key to replay it.

Accessibility

  • The root is a section labelled by its h2 title via aria-labelledby
  • Each column is a figure, with the quote in a blockquote and the name, role and company in a figcaption
  • The counting figure is hidden from assistive tech and the final value is provided as visually hidden text, so screen readers announce only the final result
  • Avatar initials, dividers, the quote mark and the contour backdrop are decorative and set aria-hidden
  • When the user's system has reduce motion enabled, columns fade in over 150ms with no movement, dividers appear immediately, figures show their final value without counting, and the contours fade in