Proof-led hero
Oversized average rating and review count lead, with a pinned featured review, award chips and press quotes as text wordmarks.
blockproofanyctatrust
$
npx shadcn@latest add @storekit/hero-proofhero-proof
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/hero-proof$
pnpm dlx shadcn@latest add @storekit/hero-proof$
yarn dlx shadcn@latest add @storekit/hero-proof$
bunx --bun shadcn@latest add @storekit/hero-proofSource
components/storekit/hero/hero-proof.tsx
164 lines
"use client"
import { motion, useReducedMotion } from "motion/react"
import { Award, BadgeCheck } from "lucide-react"
import { cn } from "@/components/storekit/lib/cn"
import { useSkBlock } from "@/components/storekit/lib/sk-analytics"
import { Stars } from "@/components/storekit/ui/stars"
/* Proof-led hero. The evidence is the headline: an oversized average
rating with stars and the verified review count, a featured review pinned
to the product image, award chips and a row of press quotes set as
wordmarks. The rating links to the reviews section. Publications render
as text so no third-party logos ship with the block; pass `logo` images
if you have the rights. Stacks proof first on mobile, then the image. */
const EASE = [0.23, 1, 0.32, 1] as const
type Cta = { label: string; href: string }
export type PressQuote = { outlet: string; quote: string; logo?: string; href?: string }
const PRIMARY =
"sk-btn inline-flex min-h-12 items-center justify-center py-2.5 text-center leading-tight rounded-sk-btn bg-sk-accent px-7 text-[15px] font-medium text-sk-accent-ink outline-none transition-[filter,transform] duration-200 ease-sk-out hover:brightness-[1.06] focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper active:scale-[0.97]"
const PRESS: PressQuote[] = [
{ outlet: "The Daily Edit", quote: "The greens powder that finally tastes good." },
{ outlet: "Wellness Weekly", quote: "Best electrolyte of the year." },
{ outlet: "Morning Report", quote: "A routine you will actually keep." },
]
export function HeroProof({
rating = 4.8,
count = 12400,
reviewsHref = "#reviews",
headline = "Loved by 12,400 verified customers.",
subcopy = "Every review is from a confirmed order. Read the good, the bad and the photos.",
primaryCta = { label: "Shop best sellers", href: "#" },
awards = ["Best Greens 2026", "Clean Label Certified"],
press = PRESS,
review = { quote: "I have tried six greens powders. This is the only one I have reordered four times.", author: "Priya S.", verified: true },
image = "/placeholder.webp",
imageAlt = "",
className,
}: {
rating?: number
count?: number
reviewsHref?: string
headline?: string
subcopy?: string
primaryCta?: Cta
awards?: string[]
press?: PressQuote[]
review?: { quote: string; author: string; verified?: boolean } | null
image?: string
imageAlt?: string
className?: string
}) {
const reduce = useReducedMotion()
const sk = useSkBlock("hero", "hero-proof")
const rise = (i: number) => ({
initial: { opacity: 0, y: reduce ? 0 : 8 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.5, ease: EASE, delay: reduce ? 0 : i * 0.07 },
})
return (
<section ref={sk.ref} {...sk.attrs} className={cn("bg-sk-paper text-sk-ink", className)}>
<div className="mx-auto w-full max-w-6xl px-4 py-10 sm:px-8 sm:py-14">
<div className="grid items-center gap-10 lg:grid-cols-[1.1fr_0.9fr] lg:gap-14">
<div>
<motion.a
{...rise(0)}
href={reviewsHref}
onClick={() => sk.emit("interact", "rating")}
aria-label={`Rated ${rating.toFixed(1)} out of 5 from ${count.toLocaleString("en-US")} reviews. Read reviews.`}
className="group inline-flex flex-wrap items-end gap-x-4 gap-y-2 rounded-sk-card outline-none focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-4 focus-visible:ring-offset-sk-paper"
>
<span aria-hidden className="font-sk-display font-semibold leading-[0.85] tracking-[-0.05em] tabular-nums" style={{ fontSize: "clamp(4.5rem, 12vw, 8.5rem)" }}>
{rating.toFixed(1)}
</span>
<span aria-hidden className="mb-2 flex flex-col gap-1.5">
<Stars value={rating} size={20} />
<span className="text-sm text-sk-muted underline decoration-sk-line underline-offset-4 transition-[text-decoration-color] group-hover:decoration-sk-ink">
{count.toLocaleString("en-US")} verified reviews
</span>
</span>
</motion.a>
<motion.h1 {...rise(1)} className="mt-6 max-w-xl text-balance font-sk-display font-semibold leading-[1.1] tracking-[-0.02em]" style={{ fontSize: "clamp(1.6rem, 3vw, 2.4rem)" }}>
{headline}
</motion.h1>
<motion.p {...rise(2)} className="mt-3 max-w-md text-[15px] leading-[1.55] text-sk-muted">
{subcopy}
</motion.p>
<motion.div {...rise(3)} className="mt-7 flex flex-col gap-4 sm:flex-row sm:items-center">
<a href={primaryCta.href} onClick={() => sk.emit("interact", "cta")} className={PRIMARY}>
{primaryCta.label}
</a>
{awards.length ? (
<ul className="flex flex-wrap gap-2">
{awards.map((a) => (
<li key={a} className="inline-flex items-center gap-1.5 rounded-sk-chip border border-sk-line px-3 py-1.5 text-[12px] font-medium">
<Award size={14} aria-hidden className="text-sk-accent" />
{a}
</li>
))}
</ul>
) : null}
</motion.div>
</div>
<motion.div
initial={{ opacity: 0, scale: reduce ? 1 : 0.97 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, ease: EASE, delay: reduce ? 0 : 0.1 }}
className="relative pb-16 sm:pb-12"
>
<div className="relative aspect-[4/3] overflow-hidden rounded-sk-card border border-sk-line bg-sk-surface lg:aspect-[4/5]">
<img src={image} alt={imageAlt} className="absolute inset-0 h-full w-full object-cover" />
</div>
{review ? (
<motion.figure
initial={{ opacity: 0, y: reduce ? 0 : 14 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 220, damping: 26, delay: reduce ? 0 : 0.35 }}
className="absolute bottom-0 left-3 right-3 rounded-sk-card border border-sk-line bg-sk-card p-4 shadow-[0_30px_60px_-30px_rgb(var(--sk-ink)/0.45)] sm:left-auto sm:right-[-12px] sm:w-[300px] lg:-left-10 lg:right-auto"
>
<Stars value={5} size={13} />
<blockquote className="mt-2 text-[14px] leading-[1.45]">{review.quote}</blockquote>
<figcaption className="mt-2.5 flex items-center gap-1.5 text-[12px] text-sk-muted">
<span className="font-medium text-sk-ink">{review.author}</span>
{review.verified ? (
<>
<BadgeCheck size={13} aria-hidden className="text-sk-savings" />
Verified buyer
</>
) : null}
</figcaption>
</motion.figure>
) : null}
</motion.div>
</div>
{press.length ? (
<motion.ul {...rise(4)} aria-label="As seen in" className="mt-10 grid gap-px overflow-hidden rounded-sk-card border border-sk-line bg-sk-line sm:grid-cols-3">
{press.slice(0, 3).map((p) => (
<li key={p.outlet} className="bg-sk-paper px-5 py-4">
<figure>
<blockquote className="text-[14px] leading-[1.45] text-sk-ink">“{p.quote}”</blockquote>
<figcaption className="mt-2">
{p.logo ? (
<img src={p.logo} alt={p.outlet} className="h-5 w-auto opacity-70" />
) : (
<span className="font-sk-display text-[15px] font-semibold uppercase tracking-[0.08em] text-sk-muted">{p.outlet}</span>
)}
</figcaption>
</figure>
</li>
))}
</motion.ul>
) : null}
</div>
</section>
)
}
Usage
Only use real ratings, awards and press quotes you can cite.
