Rating row

Stars, average, review count and one short verified quote under the title, linking to the reviews section.

componentratinganyreviews
$npx shadcn@latest add @storekit/pdp-rating-row
pdp-rating-row

Live and interactive. Switch the brand in the frame header to see the same source under another token file.

Installation

$npx shadcn@latest add @storekit/pdp-rating-row
$pnpm dlx shadcn@latest add @storekit/pdp-rating-row
$yarn dlx shadcn@latest add @storekit/pdp-rating-row
$bunx --bun shadcn@latest add @storekit/pdp-rating-row

Source

components/storekit/pdp/pdp-rating-row.tsx
65 lines
"use client"

import { BadgeCheck } from "lucide-react"
import { cn } from "@/components/storekit/lib/cn"
import { Stars } from "@/components/storekit/ui/stars"
import { useSkBlock } from "@/components/storekit/lib/sk-analytics"

/* Above-the-fold social proof that sits under the product title: stars,
   average, review count and one short verified quote. The whole row links
   to the reviews section so the proof is one tap from the full evidence. */

type Quote = { text: string; author: string; verified?: boolean }

export function PdpRatingRow({
  rating = 4.8,
  count = 1284,
  quote = { text: "Mixes clean, tastes great, and I actually look forward to it.", author: "Dana R.", verified: true },
  href = "#reviews",
  className,
}: {
  rating?: number
  count?: number
  quote?: Quote | null
  /** Anchor or URL of the full reviews section. */
  href?: string
  className?: string
}) {
  const sk = useSkBlock("pdp", "pdp-rating-row")
  const jump = (label: string) => sk.emit("interact", label, undefined)

  return (
    <div {...sk.attrs} ref={sk.ref as React.RefObject<HTMLDivElement>} className={cn("flex w-full flex-col gap-1.5", className)}>
      <a
        href={href}
        onClick={() => jump("reviews_jump")}
        aria-label={`Rated ${rating.toFixed(1)} out of 5 from ${count.toLocaleString("en-US")} reviews. Read reviews`}
        className="group/rr -mx-1 inline-flex min-h-[44px] w-fit flex-wrap items-center gap-x-2 gap-y-1 rounded-sk-btn px-1 outline-none transition-transform duration-200 ease-sk-out focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper active:scale-[0.97]"
      >
        <Stars value={rating} size={15} />
        <span className="text-sm font-semibold tabular-nums text-sk-ink">{rating.toFixed(1)}</span>
        <span aria-hidden className="h-3 w-px bg-sk-line" />
        <span className="text-sm tabular-nums text-sk-muted underline decoration-sk-line underline-offset-4 transition-colors duration-200 group-hover/rr:text-sk-ink group-hover/rr:decoration-sk-ink/40">
          {count.toLocaleString("en-US")} reviews
        </span>
      </a>
      {quote ? (
        <figure className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5 text-sm leading-[1.5]">
          <blockquote className="min-w-0 text-sk-ink">
            <p>&ldquo;{quote.text}&rdquo;</p>
          </blockquote>
          <figcaption className="inline-flex items-center gap-1 text-[13px] text-sk-muted">
            {quote.author}
            {quote.verified ? (
              <>
                <BadgeCheck size={13} aria-hidden className="text-sk-savings" />
                <span className="sr-only">, verified buyer</span>
              </>
            ) : null}
          </figcaption>
        </figure>
      ) : null}
    </div>
  )
}

Usage

Import from "@/components/storekit/pdp/pdp-rating-row" and pass your product data. The demo above exercises the full prop surface with sample values.

Rating row, StoreKit