Page Sections

Business Card Reel

Business-card-style testimonials laid out on a horizontal reel that you drag or scroll to snap; cards grow sharper and larger towards the centre and shrink, fade and tilt towards the edges.

Customer testimonials as landscape business cards on a full-width horizontal reel. Each card has an initials gradient avatar, the name and role · company at the top left, a company wordmark at the top right, a quote of up to three lines in the middle, and tags such as industry and plan below a hairline divider. The reel uses native scrolling with scroll-snap: touch and trackpads swipe directly, a mouse can press and drag with inertia, and the arrows in the header move one card at a time. Every card scales, fades and tilts with rotateY according to its distance from the reel centre, so the middle card is always the largest and clearest.

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 business-card-reel.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.

320
8
500
Active edited usageDefault props
<BusinessCardReel/>

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/business-card-reel.en.json

Or, once registries are configured in components.json, install it as @webberui/business-card-reel.

Usage

import { BusinessCardReel } from "@/components/ui/business-card-reel";

<BusinessCardReel
  eyebrow="Customer stories"
  title="The first month after rollout"
  testimonials={[
    {
      quote: "Building the weekly store rota used to take half a day. Now managers finish it in ten minutes.",
      name: "Maya Collins",
      role: "Operations Manager",
      company: "Islet Coffee",
      companyMark: "Islet",
      highlight: "in ten minutes",
      tags: ["Food & drink", "Team plan"],
    },
    // ...
  ]}
  cardWidth={320}
  tilt={8}
  onChange={(index) => console.log(`centred card ${index + 1}`)}
/>

Props

PropTypeDefaultDescription
testimonialsBusinessCardReelItem[]—Testimonial items
eyebrowstring—Small label above the title
titlestring"客戶怎麼說" (What customers say; the English registry defaults to "What customers say")Section heading, also the aria-labelledby source of the section
subtitlestring—Supporting text below the heading
valuenumber—Controlled: index of the centred card; changing it scrolls the reel to that card
defaultValuenumber0Uncontrolled: index of the initially centred card (jumps there after mount, no animation)
onChange(index: number) => void—Called when scrolling has snapped and the centred card changed
cardWidthnumber320Card width (px); height follows 1.75:1 and grows when content needs more room
tiltnumber8Maximum rotateY tilt of the side cards (deg); 0 disables tilting
showTagsbooleantrueWhether to show the tag chips at the bottom of each card
durationnumber—Entrance duration (ms); reads --wb-business-card-reel-duration when omitted (default 500ms)
reelLabelstring"客戶評價捲帶,可用左右方向鍵切換" (Customer testimonial reel, use the left and right arrow keys to move)aria-label of the reel region
prevLabelstring"上一則評價" (Previous testimonial)aria-label of the previous button
nextLabelstring"下一則評價" (Next testimonial)aria-label of the next button
classNamestring—Merged into the root section

BusinessCardReelItem

FieldTypeDefaultDescription
quotestring—Quote text, clamped to three lines on the card
namestring—Reviewer name; avatar initials use the last two characters for Chinese names and the first letters of the first and last words for Latin names
rolestring—Job title
companystring—Company name
companyMarkstring—Company wordmark text in the top-right corner; falls back to company
tagsstring[]—Tag chips at the bottom of the card (e.g. industry, plan)
highlightstring—Fragment of quote marked in the accent colour (exact match, first occurrence only)
avatarHuenumber—Avatar gradient hue (0–360); derived from the index via the 137.508° golden angle when omitted
avatarSrcstring—Avatar image URL; replaces the initials gradient tile
ratingnumber—Not shown by this component; kept so data can be shared with other testimonial components
metric{ value: string; label: string }—Not shown by this component; kept so data can be shared with other testimonial components

How it works

  • Distance mapping: each card turns the distance between its centre and the reel centre into a ratio d (0–1) and maps it to scale 1 → 0.88, opacity 1 → 0.5 and rotateY 0 → ±tilt, driven by the scroll position from useScroll({ container }) without re-rendering.
  • The track is padded by half the reel width minus half a card on each side, so the first and last cards can snap to the exact centre as well.
  • Mouse dragging temporarily disables scroll-snap, applies velocity-based inertia after release, then hands back to snapping on the nearest card; the release does not trigger clicks inside cards. Touch and trackpads keep native scrolling.
  • onChange fires only after scrolling stops and the reel has snapped; cards passed during a smooth scroll are not reported, so controlled mode is never interrupted by intermediate indexes.
  • The accent goes through --wb-business-card-reel-accent only (default #0d9488 in light, #2dd4bf in dark) for the eyebrow, quote highlight and focus ring; the entrance duration uses --wb-business-card-reel-duration.

Accessibility

  • The root is a section with aria-labelledby pointing at the heading; each testimonial is a figure with the quote in blockquote and the name and role in figcaption
  • The reel is role="region" with an aria-label and tabIndex=0; once focused, ← / → move one card and Home / End jump to either end
  • The arrow buttons are type="button" with an aria-label and aria-controls pointing at the reel, and are disabled at either end
  • After snapping, an aria-live="polite" region announces the position ("Testimonial 2 of 6") and the name
  • When the user has reduce motion enabled, distance scaling, fading and tilting are turned off, arrows jump instantly, dragging has no inertia and the entrance becomes a 150ms fade; scroll-snap is kept