import type { ComponentProps, ReactNode } from "react";
const initials = (name: string) =>
name
.split(/\s+/)
.map((part) => part[0])
.slice(0, 2)
.join("")
.toUpperCase();
const face =
"relative -ml-(--tui-social-proof-overlap) flex h-(--tui-social-proof-avatar-size) shrink-0 items-center justify-center overflow-hidden rounded-full leading-none font-semibold transition-[margin] duration-(--tui-social-proof-duration) ease-out first:ml-0 group-hover:-ml-[calc(var(--tui-social-proof-overlap)_*_0.35)] group-hover:first:ml-0 [box-shadow:0_0_0_var(--tui-social-proof-ring-width)_var(--tui-social-proof-ring)]";
export interface SocialProofProps extends Omit<ComponentProps<"div">, "children"> {
/** A few of the people. A picture is drawn if one is given, their initials if not. */
people: { name: string; src?: string }[];
/** How many more there are, in a filled capsule at the end of the row: "+2k". */
count?: ReactNode;
/** How many stars out of five. Leave it out for no stars. */
stars?: number;
/** The score itself, written beside the stars: "4.9". */
rating?: ReactNode;
/** What to say beside them. */
children: ReactNode;
}
/**
* A few faces and a line: the smallest piece of proof, for under a headline
* or beside a button. The faces overlap, and spread apart under the pointer
* so each can be seen.
*/
export function SocialProof({ people, count, stars, rating, children, className, ...props }: SocialProofProps) {
const shown = people.slice(0, 5);
return (
<div data-slot="social-proof" {...props} className={["group inline-flex items-center gap-(--tui-social-proof-gap) font-(family-name:--tui-font) text-(length:--tui-social-proof-font-size) text-(--tui-social-proof-fg)", className].filter(Boolean).join(" ")}>
<span aria-hidden className="flex shrink-0">
{shown.map((person, i) => (
<span key={person.name} data-slot="social-proof-avatar" style={{ zIndex: shown.length - i }} className={`${face} w-(--tui-social-proof-avatar-size) bg-(--tui-social-proof-avatar-bg) text-[calc(var(--tui-social-proof-avatar-size)_*_0.34)]`}>
{person.src ? <img src={person.src} alt="" className="size-full object-cover" /> : initials(person.name)}
</span>
))}
{count && (
<span data-slot="social-proof-count" className={`${face} min-w-(--tui-social-proof-avatar-size) bg-(--tui-social-proof-count-bg) px-[0.7em] text-[calc(var(--tui-social-proof-avatar-size)_*_0.32)] text-(--tui-social-proof-count-fg) tabular-nums`}>
{count}
</span>
)}
</span>
<span className="flex min-w-0 flex-col gap-1.5 leading-none">
{stars !== undefined && (
<span className="flex items-center gap-1.5">
<span role="img" aria-label={`${stars} out of 5`} className="flex gap-0.5 text-(--tui-social-proof-star)">
{[0, 1, 2, 3, 4].map((star) => (
<svg key={star} aria-hidden viewBox="0 0 16 16" fill="currentColor" className={`size-(--tui-social-proof-star-size) ${star < stars ? "" : "opacity-20"}`}>
<path d="M8 1.300l2.050 4.250 4.650.6-3.400 3.250.85 4.600L8 11.750 3.850 14l.85-4.600L1.300 6.150l4.650-.6z" />
</svg>
))}
</span>
{rating && <span className="font-semibold tabular-nums">{rating}</span>}
</span>
)}
<span className="text-(--tui-social-proof-muted)">{children}</span>
</span>
</div>
);
}:root {
color-scheme: light;
--tui-bg: #ffffff;
--tui-fg: #0a0a0a;
--tui-muted: #f5f5f5;
--tui-muted-fg: #737373;
--tui-border: #e5e5e5;
--tui-primary: #171717;
--tui-primary-fg: #fafafa;
--tui-ring: #a3a3a3;
--tui-success: #16a34a;
--tui-warning: #d97706;
--tui-danger: #dc2626;
--tui-radius: 8px;
--tui-border-width: 1px;
--tui-space: 4px;
--tui-shadow: none;
--tui-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--tui-text: 14px;
--tui-duration: 150ms;
/* Social proof */
--tui-social-proof-avatar-size: calc(var(--tui-space) * 9);
--tui-social-proof-overlap: calc(var(--tui-space) * 2.5);
--tui-social-proof-ring-width: 2px;
--tui-social-proof-gap: calc(var(--tui-space) * 3.5);
--tui-social-proof-star-size: 13px;
--tui-social-proof-font-size: calc(var(--tui-text) - 1px);
--tui-social-proof-fg: var(--tui-fg);
--tui-social-proof-muted: var(--tui-muted-fg);
--tui-social-proof-star: var(--tui-fg);
--tui-social-proof-avatar-bg: color-mix(in oklab, var(--tui-fg) 9%, var(--tui-bg));
--tui-social-proof-ring: var(--tui-bg);
--tui-social-proof-count-bg: var(--tui-primary);
--tui-social-proof-count-fg: var(--tui-primary-fg);
--tui-social-proof-duration: 360ms;
}
.dark,
[data-theme="dark"] {
color-scheme: dark;
--tui-bg: #0a0a0a;
--tui-fg: #fafafa;
--tui-muted: #1c1c1c;
--tui-muted-fg: #a3a3a3;
--tui-border: #2a2a2a;
--tui-primary: #fafafa;
--tui-primary-fg: #171717;
--tui-ring: #737373;
--tui-success: #22c55e;
--tui-warning: #f59e0b;
--tui-danger: #ef4444;
}Needs Tailwind CSS v4. Import the stylesheet once, after Tailwind.