caliper uidocs

Hero block

Block

The first screen of a product page: one claim, two actions and a real artefact that proves it.

Installs components/ui/hero-block.tsxDepends on lucide-reactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/hero-block.json
New · NetSuite two-way sync

Month-end close for finance teams

Close the books in a morning

Halden matches every bank line to the ledger as it lands, so the close is a review, not a hunt.

14 days free · no card

halden.app/close/october
October close412 of 418 matched
Mercury · Stripe payout · INV-2291rule: payouts$18,240.00Matched
AWS · Invoice 0923vendor history$4,112.37Matched
Gusto · Payroll · 31 Octamount differs by $12.40$61,904.12Review
Figma · Annual · Org planrule: SaaS$2,880.00Matched
Brex · Card settlementstatement$9,377.80Matched
Gusto · Payroll needs a look$12.40 more than the ledger. Likely: employer NI adjustment.

Closing the books at 1,200 finance teams, including

  • Ostra Freight
  • Lumen Labs
  • Kestrel Bio
  • Northwind
  • Tide Physio

When to use #

Use it for

  • A landing or product page needs its opening section.
  • You can show the product doing the thing, not a stock illustration.

Skip it when

  • Inside an app; use a page header.
  • You have no proof yet; lead with the waitlist form instead.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/hero-block.json

Adds its 4 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above.

example.tsx
import { type ReactNode } from "react"
import { BlockEm } from "@/components/ui/block-concept"
import { HeroBlock } from "@/components/ui/hero-block"
import { ProofBlock } from "@/components/ui/proof-block"

function Frame({ children }: { children: ReactNode }) {
  return <div className="w-full overflow-hidden rounded-[14px] shadow-[0_0_0_1px_var(--border)]">{children}</div>;
}

function Ledger() {
  const rows = [
    ["Mercury", "Stripe payout · INV-2291", "$18,240.00", "Matched", "rule: payouts"],
    ["AWS", "Invoice 0923", "$4,112.37", "Matched", "vendor history"],
    ["Gusto", "Payroll · 31 Oct", "$61,904.12", "Review", "amount differs by $12.40"],
    ["Figma", "Annual · Org plan", "$2,880.00", "Matched", "rule: SaaS"],
    ["Brex", "Card settlement", "$9,377.80", "Matched", "statement"],
  ];
  return (
    <div className="grid gap-4 text-[13px]">
      <div className="grid gap-2">
        <span className="text-[var(--k-muted)]">October close</span>
        <span data-slot="block-title" data-size="3" className="tabular-nums">412 of 418 matched</span>
        <div className="h-1.5 w-full max-w-[260px] overflow-hidden rounded-full bg-[var(--k-sunken)]" aria-hidden="true"><i className="block h-full w-[98.5%] rounded-full bg-[var(--k-hi)]" /></div>
      </div>
      <div className="grid">
        {rows.map(([vendor, memo, amount, state, why]) => (
          <div key={memo} className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-4 gap-y-0.5 border-t border-[var(--k-line)] py-2.5 tabular-nums @[560px]:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto_88px]">
            <span className="truncate font-medium">{vendor} <span className="font-normal text-[var(--k-muted)]">· {memo}</span></span>
            <span className="truncate text-[var(--k-muted)] @max-[559px]:hidden">{why}</span>
            <span className="text-right">{amount}</span>
            <span className={`justify-self-end rounded-full px-2 py-0.5 text-xs @max-[559px]:hidden ${state === "Review" ? "bg-[var(--k-hi-soft)] text-[var(--k-hi)]" : "text-[var(--k-muted)]"}`}>{state}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function LedgerNote() {
  return (
    <div className="grid gap-1 p-4 text-[13px]">
      <span className="font-medium">Gusto · Payroll needs a look</span>
      <span className="text-[var(--k-muted)]">$12.40 more than the ledger. Likely: employer NI adjustment.</span>
    </div>
  );
}

const heroes: Parameters<typeof HeroBlock>[0] = { eyebrow: "Month-end close for finance teams", announcement: "New · NetSuite two-way sync", title: <>Close the books <BlockEm>in a morning</BlockEm></>, lead: "Halden matches every bank line to the ledger as it lands, so the close is a review, not a hunt.", primary: { label: "Start the trial", href: "#" }, secondary: { label: "See a close", href: "#" }, note: "14 days free · no card", proof: <Ledger />, proofTitle: "halden.app/close/october", floating: <LedgerNote />, logos: ["Ostra Freight", "Lumen Labs", "Kestrel Bio", "Northwind", "Tide Physio"], logosLabel: "Closing the books at 1,200 finance teams, including" };

export function HeroBlockDemo() {
  return <Frame><HeroBlock headingLevel={2} {...heroes} /></Frame>;
}

const proof: Parameters<typeof ProofBlock>[0] = { logos: ["Ostra Freight", "Lumen Labs", "Kestrel Bio", "Northwind", "Tide Physio"], caseStudy: { figure: "9→1", figureLabel: "days to close the books at Ostra Freight", quote: "The first month we trusted it, our auditors asked for the audit trail export instead of our spreadsheets.", person: "Ana Reyes, Financial controller", action: { label: "Read the Ostra case study", href: "#" } } };

API reference #

HeroBlock

component
PropTypeDefaultDescription
eyebrowstring—
title *ReactNode—Use BlockEm for the one emphasised phrase.
lead *string—
primaryBlockAction—
secondaryBlockAction—
actionReactNode—Custom content in place of the actions, e.g. an install command or a sign-up field.
notestring—Small reassurance under the actions, e.g. "No card needed".
announcementstring—A short chip above the title, e.g. "New · NetSuite two-way sync".
proofReactNode—The product artefact that proves the claim.
proofTitlestring—The window title over the proof, e.g. "halden.app/close/october".
floatingReactNode—A small card over the stage's window bar, e.g. a result or a notification.
statsreadonly BlockStat[]—Large figures along the bottom.
logosreadonly string[]—Customer names under the hero.
logosLabelstring"Trusted by teams at"
headingLevel1 | 21
classNamestring—

Accessibility #

It is built on native elements, so roles and focus come from the browser.

No custom key handling; Tab and the native activation keys do the work.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.