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.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<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.jsonOr, 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
| Prop | Type | Default | Description |
|---|---|---|---|
testimonials | SocialPostReviewsItem[] | — | Testimonials; the first initialCount show top to bottom in array order, the rest are queued in order |
eyebrow | string | "社群回饋" (Community) | Small label above the title |
title | string | "大家正在說" (What people are saying) | Section title; also the accessible name of the section and the feed |
subtitle | string | — | Supporting text below the title |
now | string | newest postedAt | Reference time for relative timestamps (ISO 8601); never reads the current time, so server and browser output match |
live | boolean | true | Insert new posts on a timer and show the pause button |
interval | number | 4000 | Insert interval (ms); falls back to --wb-social-post-reviews-interval |
initialCount | number | 6 | Number of posts shown initially |
maxVisible | number | 8 | Maximum posts on screen; beyond this the oldest fades out at the bottom |
columns | 1 | 2 | 2 | Column count; always 1 column below 640px container width |
loop | boolean | true | Re-queue faded-out posts once the queue runs out |
showMedia | boolean | true | Show media placeholders on media posts |
paused | boolean | — | Controlled paused state; managed internally when omitted |
defaultPaused | boolean | false | Initial paused state in uncontrolled mode (always starts paused under reduce motion) |
onPausedChange | (paused: boolean) => void | — | Called when the user pauses or resumes |
className | string | — | Applied to the root section |
SocialPostReviewsItem
| Field | Type | Default | Description |
|---|---|---|---|
quote | string | — | Body text; #tags (including full-width # and CJK) and @mentions are shown in the accent color |
name | string | — | Display name; initials use the last two CJK characters or Latin first letters |
handle | string | — | Fictional handle; the @ is optional |
postedAt | string | — | Posted time (ISO 8601) |
likes | number | — | Like count |
replies | number | — | Reply count |
reposts | number | — | Repost count |
role | string | — | Job title |
company | string | — | Company or organization, joined to the role with "·" |
tags | string[] | — | Tags appended after the body (without #) |
media | boolean | false | Show a media placeholder (gradient block derived from the avatar hue) |
verified | boolean | false | Show a generic verified badge |
highlight | string | — | Fragment of the quote to mark with an accent tint |
metric | { value: string; label: string } | — | Result metric at the bottom of the post |
avatarHue | number | — | Avatar gradient hue; defaults to a golden-angle hue per index |
rating | number | — | 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
postedAtandnow, 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#0d9488light /#2dd4bfdark, 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 theh2title; the post list isrole="feed"and eacharticlecarriesaria-posinset/aria-setsizeand 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
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.
Dark Portrait Spotlight
A React testimonial section that pairs a large portrait with the quote on a dark layout; a thumbnail row below switches customers, crossfading the portrait from greyscale into colour as the name and role slide in.