Hero Section
A props-driven landing hero — eyebrow, headline, dual CTAs, social-proof bar and an optional visual that collapses to a centred column when omitted.
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/hero-section.jsonPrompt
Build a React + TypeScript + Tailwind "HeroSection" block (lucide-react Star).
Contract
- Props (all optional except title): eyebrow, title, subtitle, primaryAction,
secondaryAction, proof, visual, align?: "start" | "center",
titleAs?: "h1" | "h2" (default "h1"), plus the native <section> props and
className. Nothing is hardcoded in JSX — a buyer configures the block by
passing values.
- HeroAction is a union that makes dead buttons unrepresentable:
{ label; href: string; onClick? } | { label; onClick: handler; href?: undefined }.
An action must carry a real destination — an href (in-page "#id" that exists,
or a route) or an onClick. Never emit href="#".
- proof?: { avatars?: { name; src? }[]; rating?: number; label?: ReactNode;
logos?: ReactNode[] } — every part optional, render only what arrives.
- No hooks, no "use client": the block stays usable as a server component when
the consumer only passes href actions.
Behavior
- Layout is derived, not configured twice: align defaults to "start" when a
visual is passed and "center" when it isn't, so the block collapses into a
centred single column instead of leaving an empty half. align overrides it.
- Responsive by CONTAINER, not viewport (@container/hero): the two-column split
and the largest type step only switch on when the block's own slot is wide
(@5xl), so the same component behaves in a narrow docs column, a sidebar
preview and a full-bleed page. Type scale: text-4xl → @2xl:text-5xl →
@5xl:text-6xl.
- Overflow discipline: the copy column is a flex column with items-start, so a
child sized by fit-content can exceed its parent — an unbreakable long word
(min-content is NOT reduced by break-words) then pushes the section past the
viewport. Give the heading/subtitle w-full + break-words, and min-w-0 on the
copy column, so long headlines wrap instead of shoving the CTAs off screen.
- CTAs render as <a> when href is present and <button type="button"> otherwise;
both share one style base so the tone (primary/secondary) is the only diff.
- Proof bar: avatars overlap (-space-x-2, ring-background separators), capped at
5 with a "+N" chip; a missing src falls back to initials, never a broken img.
rating is clamped to 0..5 and dropped entirely when not finite; the star row
is aria-hidden with an "out of 5" sr-only companion. logos render as a wrapped
muted row.
Rendering & styling
- Semantic tokens only: bg-primary/text-primary-foreground for the primary CTA,
border + hover:bg-primary/10 for the secondary (a tinted hover works on any
surface, unlike hover:bg-accent over bg-muted), text-muted-foreground for
supporting copy, bg-muted/60 eyebrow pill, bg-muted/40 visual frame.
- The visual is wrapped in one aspect-ratio frame (aspect-[4/3] →
@2xl:aspect-[16/10], rounded-xl border, overflow-hidden) so any child — image,
video, live component — keeps the row stable.
- focus-visible ring-2 ring-ring with ring-offset-background on both CTAs;
cn() merges the consumer className onto the section.
Customization levers
- Density: px-6 py-16 / @2xl:py-20 and the gap-10 column gap set the vertical
rhythm; max-w-6xl on the inner row and max-w-2xl on the copy column set
measure.
- Which sub-blocks exist: omit eyebrow / subtitle / secondaryAction / proof /
visual and the layout closes up — no placeholder holes.
- Split threshold: move @5xl/hero to @4xl/hero for an earlier two-column switch,
or delete the split classes for an always-stacked hero.
- Proof emphasis: swap initials avatars for images via proof.avatars[].src, drop
the rating for a logo-only trust row, or move the logo row below the fold.
- CTA shape: add w-full @sm/hero:w-auto to the CTA base for full-bleed mobile
buttons; add a trailing lucide icon inside label (the base is a gap-2 flex).
- Motion: the block ships static; wrap the copy column in your motion library and
guard it with motion-reduce:animate-none if you want an entrance.Concepts
- Derived layout —
aligndefaults from the presence ofvisual, so removing the screenshot re-centres the hero instead of leaving a blank column; passingalignexplicitly is the escape hatch. - Container-query responsiveness — the split and the largest type step react to the block's own inline size (
@container/hero), not the viewport, so the same hero reads correctly in a narrow docs column and on a full-bleed page. - Actions that cannot be dead — the
HeroActionunion requires either anhrefor anonClick; there is no shape that renders a clickable-looking element with nothing behind it. min-contentignoresbreak-words— inside anitems-startflex column a heading is sized by fit-content, and an unbreakable long word makes that larger than the parent;w-fullre-clamps it so the headline wraps instead of pushing the CTAs out of the viewport.- Fallbacks over holes — a missing avatar
srcbecomes initials, an out-of-range rating is clamped, extra avatars fold into+N; the proof bar degrades instead of rendering broken images or 9 stars.