Skip to content
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
  • 578%
  • 414%
  • 35%
  • 22%
  • 11%
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.

Leah M.Hair studio12 Mar Verified customer

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.

Dario P.Physio clinic2 Mar Verified customer

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.

Hana K.Nail bar24 Feb Verified customer
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.