All Taste components
Testimonials
Testimonials — review summary
An average score with a star breakdown beside a short list of written reviews, for products with ratings.
average + partial stars · 5-row breakdown bars · sort select · 3 reviews with titles · write a review
Reviews
What owners say about Slotwise
4.7
Based on 312 reviews
Showing 3 of 312
Booking takes my clients about ten seconds
I added the booking link to my profile on a Sunday and had six appointments by Monday morning. The reminders sound friendly, not robotic.
Great calendar, wish exports were richer
Our team calendar finally makes sense. I’d like more columns in the monthly export, but day to day it does everything we need.
Deposits changed our weekends
Saturday slots used to vanish to no-shows. With a small deposit at booking, people turn up, and if they cancel we refill the slot.
import { Check, Star } from "lucide-react";
import { cn } from "@/lib/utils";
// Sample numbers — load your real ratings here.
const summary = { average: 4.7, total: 312, breakdown: [{ stars: 5, pct: 78 }, { stars: 4, pct: 14 }, { stars: 3, pct: 5 }, { stars: 2, pct: 2 }, { stars: 1, pct: 1 }] };
const reviews = [
{ stars: 5, title: "Booking takes my clients about ten seconds", body: "I added the booking link to my profile on a Sunday and had six appointments by Monday morning.", name: "Leah M.", business: "Hair studio", date: "12 Mar" },
{ stars: 4, title: "Great calendar, wish exports were richer", body: "Our team calendar finally makes sense. I’d like more columns in the monthly export.", name: "Dario P.", business: "Physio clinic", date: "2 Mar" },
{ stars: 5, title: "Deposits changed our weekends", body: "With a small deposit at booking, people turn up, and if they cancel we refill the slot.", name: "Hana K.", business: "Nail bar", date: "24 Feb" },
];
function Stars({ value, size = "size-4" }: { value: number; size?: string }) {
return (
<span className="relative inline-flex" role="img" aria-label={value + " out of 5 stars"}>
<span className="flex gap-0.5 text-border-strong">{[0, 1, 2, 3, 4].map((i) => <Star key={i} className={cn(size, "fill-current")} aria-hidden />)}</span>
<span className="absolute inset-y-0 left-0 flex gap-0.5 overflow-hidden text-warning" style={{ width: (value / 5) * 100 + "%" }} aria-hidden>
{[0, 1, 2, 3, 4].map((i) => <Star key={i} className={cn(size, "shrink-0 fill-current")} />)}
</span>
</span>
);
}
export function ReviewSummary() {
return (
<section aria-labelledby="reviews-title" className="bg-bg px-5 py-12 font-sans sm:px-10 sm:py-20">
<div className="mx-auto max-w-6xl">
<p className="text-[13px] font-medium text-primary">Reviews</p>
<h2 id="reviews-title" className="mt-3 text-3xl font-semibold tracking-tight text-fg sm:text-4xl">What owners say about Slotwise</h2>
<div className="mt-10 grid gap-10 lg:grid-cols-[320px_minmax(0,1fr)] lg:gap-14">
<div className="grid content-start gap-5 self-start rounded-xl border border-border bg-bg p-6">
<div className="flex flex-wrap items-end gap-3">
<p className="text-[56px] font-semibold leading-[.9] tracking-tighter tabular-nums text-fg">{summary.average.toFixed(1)}</p>
<div className="grid gap-1 pb-0.5 text-[13px] text-fg-muted">
<Stars value={summary.average} size="size-[18px]" />
<span>Based on {summary.total} reviews</span>
</div>
</div>
<ul className="grid gap-2" aria-label="Ratings breakdown">
{summary.breakdown.map((row) => (
<li key={row.stars} className="grid grid-cols-[30px_minmax(0,1fr)_38px] items-center gap-2.5 text-[13px] tabular-nums text-fg-muted">
<span className="inline-flex items-center gap-0.5 text-fg">{row.stars}<Star className="size-3 fill-current text-warning" aria-hidden /></span>
<span className="h-2 overflow-hidden rounded-full bg-surface-2"><span className="block h-full rounded-full bg-warning" style={{ width: row.pct + "%" }} /></span>
<span className="text-right">{row.pct}%</span>
</li>
))}
</ul>
<button type="button" className="h-9 rounded border border-border-strong bg-bg text-sm font-medium text-fg hover:bg-surface">Write a review</button>
</div>
<div className="min-w-0">
<div className="flex flex-wrap items-center justify-between gap-2.5 border-b border-border pb-4 text-[13.5px] text-fg-muted">
<span>Showing {reviews.length} of {summary.total}</span>
<select aria-label="Sort reviews" className="h-8 rounded border border-border-strong bg-bg px-2.5 text-[13px] text-fg">
<option>Most recent</option><option>Highest rated</option><option>Lowest rated</option>
</select>
</div>
{reviews.map((r) => (
<figure key={r.title} className="grid gap-2.5 border-b border-border py-5 last:border-b-0 last:pb-0">
<Stars value={r.stars} size="size-[15px]" />
<h3 className="text-[15px] font-semibold text-fg">{r.title}</h3>
<blockquote className="text-[14.5px] leading-relaxed text-fg-muted"><p>{r.body}</p></blockquote>
<figcaption className="flex flex-wrap items-center gap-x-2.5 gap-y-1.5 text-[13px] text-fg-subtle">
<span className="font-medium text-fg">{r.name}</span>
<span>{r.business}</span>
<span aria-hidden>·</span>
<span>{r.date}</span>
<span className="inline-flex h-[22px] items-center gap-1 rounded-full bg-success-soft px-2 text-xs font-medium text-success-fg">
<Check className="size-3" aria-hidden /> Verified customer
</span>
</figcaption>
</figure>
))}
</div>
</div>
</div>
</section>
);
}
The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.