Page Sections

Social Post Reviews

Testimonials styled as generic social posts in a two-column feed; new posts slide in from the top at intervals and like counts tick up with a small bounce.

Turns customer testimonials into a social feed that feels live. Each testimonial is a generic post card: initials avatar, display name, fictional handle, relative time and an optional verified badge, with #tags and @mentions in the body shown in the accent color, plus a reply / repost / like action row. With live on, the first few posts show right away and the rest slide in from the top at an interval while the others spring down; past the limit, the oldest post fades out at the bottom. Like counts also tick up now and then with a small bounce. When the user scrolls down to read, new posts are held behind a "N new testimonials" prompt and only inserted on click, so nothing jumps under their eyes.

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 social-post-reviews.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.

4000
Active edited usageDefault props
<SocialPostReviews/>

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/social-post-reviews.en.json

Or, once registries are configured in components.json, install it as @webberui/social-post-reviews.

Usage

import { SocialPostReviews } from "@/components/ui/social-post-reviews";

<SocialPostReviews
  eyebrow="社群回饋"
  title="大家正在說"
  initialCount={6}
  maxVisible={8}
  testimonials={[
    {
      quote: "導入三週,門市備料浪費少了一大截 #門市日常",
      name: "林語晴",
      handle: "yuching.ops",
      role: "營運主管",
      company: "青嶼咖啡",
      postedAt: "2026-10-25T05:10:00Z",
      likes: 128,
      replies: 12,
      reposts: 9,
      verified: true,
    },
    {
      quote: "排班表終於不用每週手動調三輪,謝謝 @beichen.dispatch 幫忙測試!",
      name: "王思妤",
      handle: "szuyu_w",
      company: "北辰物流",
      postedAt: "2026-10-25T03:40:00Z",
      likes: 86,
      replies: 7,
      reposts: 4,
      media: true,
    },
  ]}
/>

Props

PropTypeDefaultDescription
testimonialsSocialPostReviewsItem[]—Testimonials; the first initialCount show top to bottom in array order, the rest are queued in order
eyebrowstring"社群回饋" (Community)Small label above the title
titlestring"大家正在說" (What people are saying)Section title; also the accessible name of the section and the feed
subtitlestring—Supporting text below the title
nowstringnewest postedAtReference time for relative timestamps (ISO 8601); never reads the current time, so server and browser output match
livebooleantrueInsert new posts on a timer and show the pause button
intervalnumber4000Insert interval (ms); falls back to --wb-social-post-reviews-interval
initialCountnumber6Number of posts shown initially
maxVisiblenumber8Maximum posts on screen; beyond this the oldest fades out at the bottom
columns1 | 22Column count; always 1 column below 640px container width
loopbooleantrueRe-queue faded-out posts once the queue runs out
showMediabooleantrueShow media placeholders on media posts
pausedboolean—Controlled paused state; managed internally when omitted
defaultPausedbooleanfalseInitial paused state in uncontrolled mode (always starts paused under reduce motion)
onPausedChange(paused: boolean) => void—Called when the user pauses or resumes
classNamestring—Applied to the root section

SocialPostReviewsItem

FieldTypeDefaultDescription
quotestring—Body text; #tags (including full-width # and CJK) and @mentions are shown in the accent color
namestring—Display name; initials use the last two CJK characters or Latin first letters
handlestring—Fictional handle; the @ is optional
postedAtstring—Posted time (ISO 8601)
likesnumber—Like count
repliesnumber—Reply count
repostsnumber—Repost count
rolestring—Job title
companystring—Company or organization, joined to the role with "·"
tagsstring[]—Tags appended after the body (without #)
mediabooleanfalseShow a media placeholder (gradient block derived from the avatar hue)
verifiedbooleanfalseShow a generic verified badge
highlightstring—Fragment of the quote to mark with an accent tint
metric{ value: string; label: string }—Result metric at the bottom of the post
avatarHuenumber—Avatar gradient hue; defaults to a golden-angle hue per index
ratingnumber—Kept so data can be shared with other testimonial components; not displayed here

How it works

  • Relative times ("just now", "3h ago", "2d ago") are computed only from postedAt and now, never from the current time during render; after 30 days a UTC date is shown instead.
  • In two columns, each new post goes into the column with the lower estimated height and never changes column afterwards, so cards only get pushed down, never sideways.
  • Automatic like bumps are timed and targeted by a deterministic hash sequence rather than random numbers. Tapping the heart toggles a local liked state; the number rolls vertically and the icon scales to 1.25 and back.
  • A sentinel sits at the top of the feed: once it scrolls above the viewport, new posts collect behind a sticky "N new testimonials" prompt, which inserts them all and scrolls back to the top when clicked.
  • Rhythm and color can be overridden with CSS variables: --wb-social-post-reviews-interval (insert interval, default 4000ms, read once on mount) and --wb-social-post-reviews-accent (accent, default #0d9488 light / #2dd4bf dark, used for tags, mentions, the badge, the new-post strip and the prompt). The push-down is a spring with stiffness 300 and damping 30.
  • Updates pause automatically on hover, while focus is inside the feed, when the tab is hidden, and when the section is out of view.

Accessibility

  • The root is a <section aria-labelledby> pointing at the h2 title; the post list is role="feed" and each article carries aria-posinset / aria-setsize and can be focused with Tab
  • Each testimonial is a figure / blockquote / figcaption; the avatar and media placeholder are hidden from assistive tech, the verified badge has hidden "Verified" text, and reply and repost counts have full labels
  • New posts are announced as "New testimonial: name" through an aria-live="polite" region, at most once every 5 seconds to avoid flooding
  • A visible "Pause updates" button is provided (WCAG 2.2.2), and updates also pause on hover, focus-within and hidden tabs; the heart is a button with aria-pressed
  • When the user has reduce motion enabled, auto-updating starts paused (it can still be resumed), new posts fade in over 150ms without sliding or the spring push-down, and counts and hearts neither roll nor bounce