Skip to content
goodtabs UI
BrowseBrowse components

Hero proof

A centered hero with a proof row of numbers or logos under a hairline.

npx shadcn@latest add https://ui.goodtabs.co/r/hero-proof.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/hero-proof

goodtabs UI

Production UI, picked by a person

Free components in one calm look, installed with one command.

By the numbers

components
200
installs
12k
licence
MIT

Centered hero with a caption and three numbers.

Logos

Ship the settings page by lunch

Forms, tables and dialogs that already follow your tokens.

Used by teams at

NorthwindHalcyonParcelLatticeFieldnote

Pass names or logos as children instead of numbers.

Four numbers

Open source

A component library you can read

Copy the source, change it, keep it. No runtime package to upgrade.

stars
9.4k
contributors
148
components
212
runtime deps
0

Four numbers, two by two when narrow.

Usage

import { HeroProof } from "@/components/hero-proof"

<HeroProof
  title="Production UI, picked by a person"
  description="Free components in one calm look."
  proofLabel="By the numbers"
  proof={[{ value: "200", label: "components" }, { value: "MIT", label: "licence" }]}
/>

Props

PropTypeDefaultDescription
eyebrowstringSmall uppercase line above the title.
titleReactNodeThe headline. A string or your own element.
descriptionReactNodeOne or two sentences under the title.
actionsReactNodeButtons and links.
proof{ value: string; label: string }[]Three or four numbers for the proof row.
proofLabelstringCaption above the proof row.
childrenReactNodeLogos or custom proof, shown when proof is not set.

Source

hero-proof/hero-proof.tsx
import * as React from "react";
import { cn } from "@/lib/utils";

type Proof = { value: string; label: string };

type HeroProofProps = Omit<React.ComponentProps<"section">, "title"> & {
  /** Small uppercase line above the title. */
  eyebrow?: string;
  /** The headline. Pass a string or your own element. */
  title: React.ReactNode;
  /** One or two sentences under the title. */
  description?: React.ReactNode;
  /** Buttons and links, usually one primary and one plain link. */
  actions?: React.ReactNode;
  /** Numbers for the proof row. Omit to pass logos as children instead. */
  proof?: Proof[];
  /** Caption above the proof row, for example "Trusted by teams at". */
  proofLabel?: string;
  /** Logos or any custom proof, shown in the proof row when proof is not set. */
  children?: React.ReactNode;
};

/**
 * A centered hero with a proof row under a hairline: numbers, or logos you pass as children.
 * The row reflows by container width, so it works inside a card. Server component.
 */
export function HeroProof({
  eyebrow,
  title,
  description,
  actions,
  proof,
  proofLabel,
  children,
  className,
  ...props
}: HeroProofProps) {
  const hasRow = (proof && proof.length > 0) || children;
  return (
    <section className={cn("@container w-full py-8 @2xl:py-16 @5xl:py-24", className)} {...props}>
      <div className="mx-auto w-full max-w-6xl px-4 @2xl:px-6">
        <div className="mx-auto flex max-w-3xl flex-col items-center gap-4 text-center @2xl:gap-5">
          {eyebrow && <p className="m-0 font-mono text-xs tracking-wide text-muted-foreground uppercase">{eyebrow}</p>}
          <h1 className="m-0 text-2xl leading-none font-medium tracking-tight text-balance @2xl:text-4xl @5xl:text-6xl">
            {title}
          </h1>
          {description && (
            <p className="m-0 max-w-xl text-sm text-balance text-muted-foreground @2xl:text-base @5xl:text-lg">
              {description}
            </p>
          )}
          {actions && <div className="flex flex-wrap items-center justify-center gap-2">{actions}</div>}
        </div>
        {hasRow && (
          <div className="mt-8 border-t pt-6 @2xl:mt-14 @2xl:pt-8">
            {proofLabel && (
              <p className="m-0 mb-5 text-center font-mono text-xs tracking-wide text-muted-foreground uppercase">
                {proofLabel}
              </p>
            )}
            {proof && proof.length > 0 ? (
              <dl
                className={cn(
                  "m-0 grid gap-6 text-center",
                  proof.length >= 4 ? "grid-cols-2 @2xl:grid-cols-4" : "grid-cols-3",
                )}
              >
                {proof.map((p) => (
                  <div key={p.label} className="flex flex-col-reverse gap-1">
                    <dt className="text-xs text-muted-foreground">{p.label}</dt>
                    <dd className="m-0 text-xl font-medium tracking-tight @2xl:text-3xl">{p.value}</dd>
                  </div>
                ))}
              </dl>
            ) : (
              <div className="flex flex-wrap items-center justify-center gap-x-8 gap-y-4 text-muted-foreground">
                {children}
              </div>
            )}
          </div>
        )}
      </div>
    </section>
  );
}

For AI agents

Hand your agent this page, the markdown at /components/hero-proof/prompt.md, or the Copy AI prompt button above.

When to use

  • A landing hero where numbers or customer names make the case
  • When you have three or four real figures, or five or fewer logos
  • Inside a column or card, since it reflows by container width

When not to use

  • When there is no proof yet; use the plain hero
  • A product-first page, which wants the split hero
  • With invented numbers or logos you do not have the right to show

Agent notes

  • Reflows on container width (@2xl); numbers stay three across, four become two by two when narrow
  • Pass either proof or children; proof wins when both are set
  • Logos are slots: give them text-muted-foreground, they inherit the color
  • Server component; the proof row is a dl, so numbers read as term and value