Reviews React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React reviews section for products and sellers: average, stars, a 5 to 1 filter breakdown, and sortable reviews with photos and replies.
The summary shows the average, its stars and how many reviews there are; the five bars grow in one after another the first time they scroll into view. Tap or click a bar to see only that many stars (it stays highlighted, the other bars dim), tap it again to see all; the chips above the list do the same and add "With photos", and the list reflows with the reviews sliding into place. Sort opens a menu on a laptop and a bottom sheet on a phone. Long reviews stop after a few lines with "Show more", which springs the rest open. Tap a photo to open it full screen (swipe between photos, pinch or double tap to zoom, swipe down or press Escape to close, and focus returns to the photo). "Helpful" votes at once and rolls back if saving fails; the count moves by one with it. On a wide container the summary sits in a sticky column beside the list; below that it stacks above. Keyboard: Tab through the bars, the sort, the chips (arrow keys move between chips), each review's photos, Show more, Helpful and Report; Space or Enter toggles a bar, a chip or Helpful and opens a photo; in the photo viewer Left and Right change the photo and Escape closes it; in the sort menu the arrow keys pick an order and Enter confirms. Every bar, chip and button has a 44px target on touch.
Reviews is a Pro React component in the React commerce components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add reviews, together with Avatar, Badge, Button, Collapsible, Empty State and 16 more and the npm packages lucide-react and motion.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add reviewsReviews is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import Reviews from @/components/ui/reviews.
import { Reviews, type ReviewData, type ReviewsLabels } from "@/components/ui/reviews"
const labels: Partial<ReviewsLabels> = {
title: "Customer reviews",
write: "Review this product",
}
const reviews: ReviewData[] = [
{
id: "r1",
author: { name: "Julia Davis", avatar: "/demo/ioana-dumitrescu.jpg" },
rating: 5,
title: "Exactly what I wanted",
body: "Fast delivery with UPS, and the bike arrived almost assembled.",
date: "2026-09-22T10:15:00",
photos: ["/demo/bicycle.jpg"],
verified: true,
helpfulCount: 12,
},
]
export function ProductReviews() {
return (
<Reviews
reviews={reviews}
summary={{ average: 4.6, count: 126, distribution: [92, 21, 7, 3, 3] }}
tone="warning"
labels={labels}
onHelpfulChange={async (id, helpful) => {
const res = await fetch(`/api/reviews/${id}/helpful`, { method: helpful ? "POST" : "DELETE" })
if (!res.ok) throw new Error("vote failed")
}}
onWriteReview={() => {}}
/>
)
}Props
reviewsdefault"[]"ReviewData[]The loaded page of reviews (empty shows the empty state): { id, author: { name, avatar?, href? }, rating, title?, body?, date, photos?, verified?, helpfulCount?, helpful?, reply?: { author, body, date? }, meta?, context? }.
summarydefault"summarizeRatings(reviews)"ReviewsSummaryData{ average, count, distribution: [n5, n4, n3, n2, n1], aspects?, photoCount? }. Pass the server's numbers when the list is paged; the bars always use count for their proportions and the average shows one decimal.
layoutdefault"split""split" | "stacked"split: on a container of 768px or more the summary becomes a sticky column beside the list. stacked: the summary sits above the list, with the average beside the bars once there is room.
showSummarydefaulttruebooleanThe average, the stars, the count and the breakdown.
showDistributiondefaulttruebooleanThe 5 to 1 star bars in the summary.
filterabledefaulttruebooleanThe bars become toggle buttons and the chips row appears (All, With photos, one chip per star).
sortabledefaulttruebooleanThe sort select above the list (a bottom sheet on phones).
defaultSortdefault"relevant""relevant" | "newest" | "highest" | "lowest"The starting order. relevant keeps the order the reviews came in; the others sort the loaded reviews (ties newest first).
sortReviewsSortControlled sort.
onSortChange(sort: ReviewsSort) => voidCalled when a person picks an order. When given, the list is not sorted locally: reload it in the new order.
filterReviewsFilterControlled filter, { rating?: 1 to 5; withPhotos?: boolean }.
defaultFilterdefault"{}"ReviewsFilterThe starting filter. Filters apply to the loaded reviews; a caller that reloads per filter gets the same result.
onFilterChange(filter: ReviewsFilter) => voidCalled when a bar or a chip changes the filter.
onHelpfulChange((id: string, helpful: boolean) => void | Promise<unknown>)Shows the Helpful vote on every review. It flips at once; return a promise to keep it pending, and a rejection rolls it back and announces labels.helpfulError.
onReport(id: string) => voidShows a Report button on every review.
onWriteReview() => voidShows "Write a review" in the summary and in the empty state.
maxLinesdefault4numberLines of a review's text before "Show more". The toggle only appears when the text is longer.
photoLightboxdefaulttruebooleanReview photos are buttons that open a full-screen viewer (zoom, swipe, swipe down to close).
loadingdefaultfalsebooleanFirst load: a skeleton summary and skeleton reviews in the real shape.
skeletonCountdefault3numberSkeleton reviews while loading.
hasMoredefaultfalsebooleanShows a full-width "Show more reviews" button with "Showing 8 of 126" under the list.
onLoadMore(() => void | Promise<unknown>)Loads the next page. A returned promise shows the loading state until it settles.
loadingMoredefaultfalsebooleanThe next page is loading (for callers that do not return a promise). Shows with hasMore.
emptyStateReactNodeReplaces the default empty state ("No reviews yet" with a Write a review action).
errorReactNodeShows an error state with the message in place of the list.
onRetry() => voidAdds a Try again button to the error state.
sizedefault"md""sm" | "md"md for a product page; sm for sheets, sidebars and seller cards (a 32px average, smaller stars, 56px photos).
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"The stars, the bars and a voted Helpful chip. neutral is black / white; amber stars are tone="warning".
colordefault""stringAny CSS color for the stars and bars; replaces the tone.
nowdefault"useNow()"number | nullThe reference time in ms for "2 days ago"; older than 28 days prints the date. null prints dates only. A timestamp, so it stays out of the playground.
linkComponent(props: LinkComponentProps) => ReactNodeThe router link for author.href (pass next/link's Link). Defaults to the one given to LinkProvider (lib/navigation), else a plain <a>.
localedefault""stringNumbers and dates ("4.6", "126", "9/22/2026"). Empty uses the app's or the browser's locale.
labelsPartial<ReviewsLabels>Every string, English by default: title, average(value), count(n, formatted), stars(n), all, withPhotos(n, formatted), filters, distribution, aspects, sort, sortOptions, verified, helpful, helpfulCount, helpfulError, report, reply, showMore, showLess, write, empty, emptyDescription, emptyFiltered, emptyFilteredDescription, showAll, errorTitle, retry, loadMore, loadingMore, remaining, showing, photos, photo(i, n), photoError.
lightboxLabelsPartial<ImageLightboxLabels>Strings of the full-screen photo viewer (close, next, previous, zoom…).
classNamesPartial<Record<"summary" | "distribution" | "bar" | "toolbar" | "list" | "review" | "photos" | "reply" | "footer", string>>Extra classes for the inner parts.
classNamestringExtra classes for the root, merged last.
Parts
ReviewsSummary
componentThe summary on its own (a seller card, a product header): summary, layout, size, tone, color, showDistribution, filterable, selectedRating, onSelectedRatingChange, onWriteReview, animated (default true: the bars grow in on first view; false shows them filled), locale, labels, classNames. Props: ReviewsSummaryProps.
ReviewItem
componentOne review as an <article>: review, size, tone, color, maxLines, photoLightbox, onHelpfulChange, onReport, now, linkComponent, locale, labels, lightboxLabels, classNames. Props: ReviewItemProps.
summarizeRatings
function{ average, count, distribution: [n5, n4, n3, n2, n1] } from a list; ratings round to the nearest star. Signature: (reviews: { rating: number }[]) => ReviewsSummaryData.
DEFAULT_REVIEWS_LABELS
constantThe English copy of the summary, filters, sort menu, review items and empty states. Spread it into labels when you translate only a few keys.
ReviewData
typeOne review: id, author { name, avatar?, href? }, rating, title, body, date, photos, verified, helpfulCount, helpful, reply { author, body, date? }, meta and context.
Examples
Stacked on a phone
The summary above the list, bars full width, chips that scroll sideways and a sort sheet.
Seller reviews
Aspect scores under the bars and what each buyer bought, as context on every review.
Loading, then content
Skeletons in the real shape, then the reviews fade in.
Empty
No reviews yet, with the action that writes the first one.
Filtered
Filtered to 1 star or to reviews with photos. The filter is controlled; bars and chips follow it.
Small, inside a drawer
size="sm" in a sheet on phones and a side panel on a laptop.
Error and retry
A failed load shows the message and a retry that brings the list back.
Summary with a custom color
ReviewsSummary on its own, stars and bars in any CSS color.
Dependencies
Files
- components/ui/reviews.tsx
FAQ
Is the React Reviews component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Reviews?
Run npx wingo-ui@latest add reviews. It also adds the Wingo UI items it builds on (Avatar, Badge, Button, Collapsible, Empty State and 16 more) and the npm packages lucide-react and motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/reviews.
How does Reviews behave on phones?
Sort opens a menu on a laptop and a bottom sheet on a phone.
See also
Used in
More React commerce components
Articles using this component
- React Rating Stars: Half Values, Hover and Keyboard
Component guides9 min read