Skip to Content
BlocksProductHeroBlock

ProductHeroBlock

Explains the product, shows a relevant example, and leads to a primary next step.

Plan idea 49, category Marketing & conversion. Status experimental — see verification.

Import

import { ProductHeroBlock } from "@raydenui/ui/blocks"; import type { ProductHeroBlockProps, ProductHeroCta } from "@raydenui/ui/blocks";

Usage

<ProductHeroBlock eyebrow="Logistics platform" eyebrowBadge="New" headline="Every shipment, one clear view" description="Track freight across carriers without stitching together four dashboards." primaryCta={{ label: "Start free", href: "/signup" }} secondaryCta={{ label: "Book a walkthrough", href: "/demo" }} note="No credit card required." highlights={[ { id: "uptime", label: "Uptime", value: "99.98%" }, { id: "carriers", label: "Carriers connected", value: "240" }, ]} media={{ src: "/images/shipment-board.png", alt: "Shipment board showing twelve active consignments grouped by carrier", caption: "The shipment board after a morning sync.", }} />

A call to action is a destination or an action, never both

ProductHeroCta is a union. Supply href for a destination, which renders a native anchor, or onClick for an in-page action, which renders a native button. A control with neither cannot be configured.

<ProductHeroBlock headline="See it on your own data" primaryCta={{ label: "Open the sandbox", onClick: () => setSandboxOpen(true) }} secondaryCta={{ label: "Read the docs", href: "/docs", external: true }} />

Heading level

headingLevel defaults to "h1", because a hero usually owns the page heading. Drop it to "h2" when the page already has an h1.

<ProductHeroBlock headingLevel="h2" headline="Built for operations teams" />

Text-only, centred

align="center" applies only when there is no media.

<ProductHeroBlock align="center" headline="One place for freight decisions" description="Start with the shipments that need you today." primaryCta={{ label: "Start free", href: "/signup" }} />

Loading and error

<ProductHeroBlock state="loading" headline="Loading" loadingLabel="Loading the hero example" />
<ProductHeroBlock state="error" headline="Every shipment, one clear view" errorTitle="The example could not load" errorDescription="The product screenshot is temporarily unavailable." onRetry={() => refetch()} retryLabel="Try again" />

The retry control is omitted entirely when onRetry is absent, so the block never shows a button with nowhere to go.

Props

PropTypeDefaultDescription
headlineReactNode—Required. The product statement.
eyebrowReactNode—Small label above the headline. Not a heading.
eyebrowBadgestring—Badge beside the eyebrow, e.g. a release tag.
descriptionReactNode—Supporting explanation.
headingLevel"h1"–"h6""h1"Element for the headline. Highlights use the next level down.
primaryCtaProductHeroCta—Primary next step.
secondaryCtaProductHeroCta—Alternative step.
noteReactNode—Small print under the actions.
highlightsProductHeroHighlight[]—Proof points, rendered as a list.
mediaProductHeroMedia—A relevant example image.
mediaSlotReactNode—Live demo or embed. Takes precedence over media.
mediaFallbackReactNode—Replaces the media when the image fails or src is empty.
align"start" | "center""start"Text alignment when there is no media.
state"default" | "loading" | "error""default"Lifecycle owned by the consuming application.
loadingLabelstring—Announced while loading.
errorTitlestring—Shown while state="error".
errorDescriptionReactNode—Shown while state="error".
onRetry() => void—Retry handler. The control is omitted without it.
retryLabelstring—Retry control label.
classNamestring—Lands on the section root.

ProductHeroBlock forwards its ref to the section element and accepts the remaining HTMLAttributes<HTMLElement>.

ProductHeroCta

MemberTypeDescription
labelstringRequired.
iconIconNameOptional leading icon.
hrefstringDestination. Renders an anchor.
externalbooleanOpens a new tab and adds rel="noreferrer".
onClick(event) => void | () => voidAnalytics hook with href; the action without it.

ProductHeroHighlight

MemberTypeDescription
idstringRequired, stable.
labelstringRequired, e.g. “Uptime”.
valueReactNodeEmphasised value, pre-formatted by the caller.
iconIconNameOptional.

ProductHeroMedia

MemberTypeDefaultDescription
srcstring—Required.
altstring—Required. Describes the example, not the product.
aspectRatio"auto" | "video" | "wide" | "square""auto"auto keeps the image’s proportions so a screenshot is never cropped.
widthnumber—Intrinsic width.
heightnumber—Intrinsic height.
captionReactNode—Rendered under the media.

States

  • default — headline, description, actions, highlights, media.
  • loading — placeholder shapes marked aria-hidden, a role="status" announcement, and aria-busy on the section.
  • error — a role="alert" panel with an optional retry control.
  • missing media — a labelled role="img" placeholder. Not a broken image and not a silent gap.
  • No empty state. headline is required: a hero without one is not a hero.
  • No success state. The block submits nothing, so it never claims an outcome.

Verification

Measured 22 September 2026 with Playwright 1.58.2 driving installed Google Chrome and axe-core 4.11.1, at 320, 390, 768 and 1440 CSS pixels in light and dark mode, plus a 358px container on a 1440px viewport and 200% root text sizing. Re-run in the repository story suite on 23 September 2026.

Not verified: a screen-reader pass, any non-Chromium engine, operating-system text-size or full-browser-zoom settings, or real photographic assets — the stories use inline SVG data URIs so they render offline.

Known limitations

  • The block renders its own call-to-action recipe rather than composing Button, which cannot render an anchor. That duplication will drift until Button gains a polymorphic escape hatch.
  • Target size was not measured for every control.
Last updated on