Hero
ProA centered marketing hero section. Named parts you assemble: an announcement eyebrow, a balanced headline, a subtitle, a CTA row, a check-circle feature list and an integrated social-proof row.
Installation#
Usage#
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Anatomy#
import {
Hero,
HeroContent,
HeroColumn,
HeroTitle,
HeroHighlight,
HeroSubtitle,
HeroActions,
HeroFeatures,
HeroFeature,
HeroSocialProof,
HeroRating,
HeroMedia,
HeroBackground,
HeroCursors,
HeroCursor,
HeroScatter,
HeroScatterTile,
HeroStage,
HeroStageMedia,
HeroStageTitle,
HeroStageCard,
HeroStageReveal,
HeroSpread,
HeroSpreadMedia,
HeroSpreadPanel,
HeroSpreadIndex,
HeroStatement,
HeroSignoff,
HeroMosaic,
HeroFrame,
} from "@/components/ui/hero"
<Hero>
<HeroBackground />
<HeroScatter>
<HeroScatterTile />
</HeroScatter>
<HeroCursors>
<HeroCursor />
</HeroCursors>
<HeroContent>
<HeroColumn>
<HeroTitle>
<HeroHighlight />
</HeroTitle>
<HeroSubtitle />
<HeroActions />
<HeroFeatures>
<HeroFeature />
</HeroFeatures>
<HeroSocialProof>
<HeroRating />
</HeroSocialProof>
</HeroColumn>
<HeroMedia />
</HeroContent>
</Hero>
{/* Alternative bodies, each in place of HeroContent */}
<HeroStage>
<HeroStageMedia>
<HeroStageTitle />
</HeroStageMedia>
<HeroStageCard />
<HeroStageReveal />
</HeroStage>
<HeroSpread>
<HeroSpreadMedia />
<HeroSpreadPanel>
<HeroSpreadIndex />
<HeroColumn />
<HeroColumn />
</HeroSpreadPanel>
</HeroSpread>
<HeroContent>
<HeroMosaic>
<HeroFrame />
</HeroMosaic>
<HeroStatement />
<HeroSignoff />
</HeroContent>Minimal#
Drop the features and social-proof rows for a lean hero: just the eyebrow, title, subtitle, and CTAs. Override the vertical rhythm on HeroContent when you want it tighter.
Display title#
size on HeroTitle sets the headline's scale. The default "lg" peaks at 60px, level with a SectionHeader size="lg"; "xl" is the display cut for the page's one big statement, peaking at 72px. The tracking stays tight: DM Sans follows its optical-size axis, so at display sizes it already sets in its tighter cut. Widen the column with max-w-5xl on HeroContent so a display headline keeps two lines, and give the rating size="md" stars so the proof row holds its own under the bigger type.
Rotating highlight#
Pass rotate a list of phrases to HeroHighlight and the wash becomes a rotating zone: the words inside swap, each entering with a per-character stagger while the previous one clears, the wash hugs each word (tweening its width as the phrase changes), and each phrase takes the next color in the palette (semantic DS roles, so it tracks every theme), the tint crossfading as it swaps. Lead every phrase with the same word so the left edge stays anchored against the copy beside it and only the right edge resizes. Give the title a touch more line-height (the demo uses leading-[1.2]) so the wash always clears the line above it. It holds each phrase for interval ms (default 2600) and honors prefers-reduced-motion by holding the first phrase with no cascade.
Cursors#
Add a HeroCursors layer and place HeroCursors in it for the feel of a live, shared design file. Each cursor wanders its own slow loop, pauses twice, and clicks at each pause: the arrow presses and a soft ripple leaves the tip. Give every cursor a different duration (seconds per loop, default 16) and phase (0 to 1) so they never move in step. color takes a semantic role (brand, purple, teal, pink, orange, success), so the fills follow every theme, and facing points the arrow left or right; the name tag always hangs on the side away from the point. Place each cursor's tip with className, and resize its loop with [--cursor-x:…] / [--cursor-y:…] (a negative x mirrors it).
Pass labels (an array of names) instead of children and the name tag rolls to the next one on every click, twice a loop: the old name lifts out, the new one rises in, and the pill eases to its width, the same AnimatedLabel roll every changing label in Koala uses. The swap is timed off the cursor's own loop, so it always lands as the arrow presses, and it stops whenever the cursor is hidden.
The layer is decoration only. It is hidden from assistive tech, lets every click through to the copy underneath, and shows its cursors only when the hero itself is at least 64rem wide (a container query, so it reads the hero's width, not the window's), which keeps them in the gutters and off the headline on smaller frames. When your cursors roam space the copy never reaches (the bands above and below it, as in this demo), reveal them sooner with className="@2xl:block" (a 42rem hero). Under prefers-reduced-motion they hold still where you placed them, with no ripple.
Poster wall#
When the product itself is a wall of covers (events, classes, listings, releases), open on that content instead of a screenshot of it. A HeroScatter layer fills the band with tilted HeroScatterTile prints and the copy stands in the clearing at its centre. Each tile takes a size (sm / md / lg, square, stepping up twice as the hero widens), a depth (far sits it smaller and lighter, further back), a tilt in degrees, and a delay, so the wall assembles card by card on mount. Place each one with className; a negative offset (-left-4) lets the band crop it, which is what makes the wall read as bigger than the page.
Give the copy a taller pt / pb than usual: that clearing is what keeps the top and bottom cards off the words where there are no gutters to put them in. The layer is a size container, so hold the rest back until the hero has earned the room (hidden @5xl:block for the sides of a 48rem copy column). Like the cursors it is decoration only: hidden from assistive tech, never in the way of a click, and still under prefers-reduced-motion. The copy on top of it stays the house stack, and the headline goes plain: the wall is already the colour on this band, and a HeroHighlight wash under a 72px line reads as a slab rather than an accent.
Feature columns#
HeroFeatures has a second shape. Set layout="columns" and the inline check row becomes three capability columns, each a bare mark in full ink over a title and a sentence: pass icon and title to every HeroFeature inside and the sentence stays as its children. Reach for it when the three claims each deserve a sentence rather than three words, and the page has no room for a separate feature section above the fold.
Nothing is drawn between the columns: no rule, no padding, no surface, so the gutter carries the separation and stays wide enough to do it. It is a grid, not a wrapping row, and every column subgrids onto the same three tracks, so a title that wraps to two lines pushes all three sentences down together instead of stranding its neighbours half a line high. Below the sm breakpoint the tracks collapse and the columns stack. The row follows the hero it sits in: centered under a centered column, flush-left in a split, and white over background media.
Split layout#
Set layout="split" on Hero to turn the content into a two-column grid: a HeroColumn of copy beside a HeroMedia visual (an image, screenshot, or logo wall). It folds back to a single stacked column below the lg breakpoint. The same parts read the layout from context, so the only change is the prop.
Background media#
Set layout="background" and add a HeroBackground to turn the hero into a full-bleed band: the media covers the section, a gradient scrim lays over it, the copy flips to white, and the eyebrow goes glassy, all from the one prop. Drop a plain <img> in for a photo, or an autoPlay muted loop playsInline <video> for ambient motion (muted autoplay is the only kind browsers allow unprompted). Give the video a poster so the band never flashes empty, and a still <img className="hidden motion-reduce:block"> fallback so it holds a frame under prefers-reduced-motion.
Scroll stage#
HeroStage is the hero that plays itself as the reader scrolls: one photograph opens full-bleed under a display line, contracts into a centred card, and a collage assembles around it while the copy rises underneath. Several things move at once rather than one: the frame contracts, the picture inside it pushes in on its own slower beat (so the shot gains depth instead of just getting smaller), the display line grows past the frame and defocuses as the camera goes through it, and each collage piece flies in from its own edge, leaning, and settles level. Once landed, the pictures keep a slow drift inside their frames, each on its own clock, so the composition breathes instead of freezing when the reader stops. All of it is scrubbed by the scroll position through a single CSS view timeline, so there is no animation library, no scroll listener and no requestAnimationFrame loop anywhere in it: it runs backwards when the reader scrolls back up, and costs nothing off screen. Compose it in place of HeroContent. Every part is droppable, so a stage holding nothing but a HeroStageMedia is a picture that contracts.
It spends three viewports of scrolling on a single statement, so it is a page's one cinematic moment, not a section to reach for twice. Browsers with no scroll-driven animations, and readers who asked for reduced motion, get a single viewport holding the last frame: the picture at its card size, the collage assembled around it, and the eyebrow, lede and CTAs where they land. Only the display word is missing there, since a word cut to fill a screen cannot sit inside a card, so put an ordinary HeroTitle in the closing HeroStageReveal when the section needs a headline visible at rest. See the scroll stage section for the finished slab.
Parallax cover#
layout="cover" is the editorial opener: one picture or clip takes the whole viewport (100svh, so a phone's shrinking browser chrome can never cut the words off) and the copy stands along its bottom edge, the headline in one corner and the paragraph in the other. Where layout="background" centres a copy column in the middle of the band and treats the media as a backdrop for it, here the media is the statement and the words are a caption laid along the foot of it, so the frame is never covered. Set the headline to size="2xl", the 96px poster cut, and keep it to two or three words.
It does not stay full bleed. As the reader scrolls, the frame pulls in to the page's own container (90rem, the max-w-8xl every other section sits in) and rounds its corners, so the opening picture hands the page back to its grid instead of ending at a hard seam. One registered number, --hero-cover-t, animates on the band and the frame's width, its corner and the caption's padding are calc()s reading it, so they cannot drift out of step: the caption steps in by exactly what the frame pulls in by and the words hold the same distance from the picture's edge from the first frame to the last, at every width. Retune the landing with [--hero-cover-gutter:…], [--hero-cover-max:…] and [--hero-cover-radius:…], and when it runs with [--hero-cover-contract-start:…] / -end (a hero opens at the top of the page, which is already halfway through its own cover range, so the beat starts at 50% and finishes half a viewport of scrolling later).
The parallax is one prop: <HeroBackground parallax>. The media travels slower than the band it fills while the caption over it travels a little faster, so the two planes separate and the band reads as a window rather than a picture glued to the page. It is scrubbed by the scroll position through a CSS view timeline, the same engine the scroll stage runs on, so there is no animation library (parallax is the effect people install GSAP and Lenis for), no scroll listener and no requestAnimationFrame loop: it runs on the compositor, off the main thread, goes backwards when the reader scrolls back up, and costs nothing off screen. The media is held a quarter larger than its frame for the whole travel, which is the slack the drift moves within, so give it a picture with room around its subject. Retune with [--hero-parallax-travel:…] and [--hero-parallax-zoom:…] on the hero, keeping travel at or under (zoom - 1) / 2 or the frame will show its ground at the ends of the drift.
Browsers with no scroll-driven animations, and readers who asked for reduced motion, get the same hero full bleed and still, with the media sitting level in its frame: a parallax has a neutral frame, and the neutral frame is a finished, full-bleed cover. The drift also works under layout="background", which declares the same timeline. See the parallax cover section for the finished slab.
API reference#
Hero
The full-bleed <section> that provides the styling context; every part reads its layout. Forwards all native <section> props; every part accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| layout | "centered" | "split" | "background" | "cover" | "centered" | One centered column; copy beside a HeroMedia; light copy centered over a HeroBackground; or a full-viewport picture with the copy along its bottom edge. |
| CSS variable | Description |
|---|---|
| --hero-parallax-travel | How far a parallax background drifts. Keep it at or under (zoom - 1) / 2. |
| --hero-parallax-zoom | How much larger than its frame a parallax background is held, the slack the drift moves within. |
| --hero-cover-gutter | cover: what the contracted frame leaves at the page edge. |
| --hero-cover-max | cover: the container the frame lands in (the site's 8xl by default). |
| --hero-cover-radius | cover: the corner the frame rounds to (radius-3xl by default). |
| --hero-cover-pad | cover: the caption's padding inside the frame, before the gutter is added. |
| --hero-cover-contract-start | cover: where in the scroll the contraction starts (with -end). |
HeroContent
The max-width content column: the stacked column itself when centered, the two-column grid holding a HeroColumn and a HeroMedia when split. Forwards all native <div> props.
HeroColumn
Groups the copy stack in a split hero or a spread panel. Forwards all native <div> props.
HeroTitle
The balanced <h1>; the pill above it is the standalone Announcement (variant="glass" over media), not a Hero part. Forwards all native <h1> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "lg" | "xl" | "2xl" | "lg" | Peaks at 60px; xl is the 72px display cut; 2xl the 96px poster cut for the foot of a cover. |
HeroHighlight
A keyword marker inside a title or statement: a soft ink wash, the only way Koala marks a word in a headline (no gradient text). Forwards all native <span> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| rotate | string[] | - | Cycle a fixed zone through these phrases, each entering with a per-character stagger and its own color. Needs two or more; holds the first under reduced motion. |
| interval | number | 2600 | Milliseconds each phrase holds before the next swaps in. |
HeroSubtitle
The muted lead paragraph. Forwards all native <p> props.
HeroActions
The CTA row: drop Buttons in, no size needed. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "xl" | "xl" | Control height imposed on the children; an explicit one still wins. Pass lg beside an inline signup Input so field and button match. |
| Data attribute | Description |
|---|---|
| data-control-size | The imposed control size. |
HeroFeatures
The feature list, a <ul>. Forwards all native <ul> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| layout | "inline" | "columns" | "inline" | A wrapping check-circle checklist, or three hairline-divided capability columns whose items take icon and title. |
| balance | boolean | false | Caps the row at a 36rem measure so items wrap into even rows (2 + 2, not 3 + 1). Ignored by columns. |
| Data attribute | Description |
|---|---|
| data-layout | "inline" or "columns". |
HeroFeature
One item, an <li>: inline it draws its own green check-circle before the label; in columns, given a title, it becomes a column with children as the sentence. Forwards all native <li> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| icon | React.ReactNode | - | columns only: the bare mark in full ink, sized to 20px. Give every item in the row one, or none. |
| title | React.ReactNode | - | columns only: the one-line claim. Without it the item stays an inline check row. |
HeroSocialProof
The proof row: an AvatarGroup beside a HeroRating. Forwards all native <div> props.
HeroRating
A read-only Rating with its label ("5.0 Ratings") as children. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | 5 | Filled stars. |
| max | number | 5 | Total stars. |
| size | "sm" | "md" | "lg" | "sm" | Star size: 16px beside a one-line label, md (20px) beside a larger avatar stack. |
HeroMedia
The visual region: the second column of a split hero, or below the copy when centered. Drop a screenshot, product window or logo wall in; forwards all native <div> props.
HeroBackground
The full-bleed media layer for background and cover: stretches a child <img> or <video> to cover the band under a gradient scrim. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| parallax | boolean | false | Drift the media against the page on scroll through a CSS view timeline; still under reduced motion. Give it a picture with room around its subject. |
| Data attribute | Description |
|---|---|
| data-parallax | Present when parallax is on. |
HeroCursors
The decorative multiplayer-cursor layer: covers the hero, aria-hidden, lets clicks through, and shows its cursors once the hero is 64rem wide. Forwards all native <div> props.
HeroCursor
One wandering cursor, positioned by its tip via className (left-[12%] top-[20%]); children is the name tag. Holds still under reduced motion; forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| color | "brand" | "purple" | "teal" | "pink" | "orange" | "success" | "brand" | The cursor and name-tag fill. |
| facing | "left" | "right" | "left" | Which way the arrow points; the label hangs on the other side. |
| duration | number | 16 | Seconds per loop. Vary it per cursor. |
| phase | number | 0 | Where in its loop it starts, 0 to 1, so siblings never move in step. |
| labels | readonly string[] | - | Names the tag rolls through, one per click, in place of children. |
| Data attribute | Description |
|---|---|
| data-color | The color value. |
| data-facing | "left" or "right". |
| CSS variable | Description |
|---|---|
| --cursor-x | Horizontal reach of the loop (28px by default); negative mirrors it. |
| --cursor-y | Vertical reach of the loop (20px by default). |
HeroScatter
The poster-wall layer behind the copy: covers the hero, aria-hidden, never blocks a click, and is a size container. Forwards all native <div> props.
HeroScatterTile
One photo print in the wall, placed by className (left-[8%] top-16, hidden @5xl:block) around a plain <img> it sizes and crops. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | The square card size; each steps up as the hero widens. |
| depth | "near" | "far" | "near" | far steps the card back into the page behind the near ones. |
| tilt | number | 0 | Degrees it leans, positive clockwise; keep the wall between about -12 and 12. |
| delay | number | 0 | Milliseconds before it drifts in, so the wall assembles card by card. |
HeroStage
The scroll stage, composed instead of HeroContent: a track taller than the viewport with one pinned screen, the only part that declares the timeline. Forwards all native <div> props.
| CSS variable | Description |
|---|---|
| --hero-stage-track | Total scroll length of the section (default 280svh). |
| --hero-stage-w | Width of the card the picture lands on. |
| --hero-stage-h | Height of the card the picture lands on. |
| --hero-stage-radius | The landed card's corner. |
| --hero-stage-stagger | Scroll gap between stagger steps (4% of the pin by default). |
HeroStageMedia
The frame that contracts from full-bleed to a card, covering a plain <img> or <video>; put HeroStageTitle inside it. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| scrim | boolean | true | A bottom-weighted gradient that carries the display word and leaves with it. |
HeroStageTitle
The section's <h1> at poster scale, trimmed by the frame; two or three words. A plain string leaves character by character, with the heading keeping the text as its label. Forwards all native <h1> props.
| CSS variable | Description |
|---|---|
| --hero-stage-exit-stagger | Gap between two letters leaving; 0 turns the cascade off. |
| --hero-stage-exit-start | Where the exit window starts (with --hero-stage-exit-end). |
| --hero-stage-letter-travel | How far a letter slides out of its mask (default -140%); raise it for deep descenders. |
HeroStageCard
One collage photograph that arrives around the landed card, placed and sized with inset utilities (never translate-* or scale-*). Its sway derives from index; forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| index | number | 0 | Place in the stagger: 0 lands first, each step a little further into the scroll. Number parts in landing order, not DOM order. |
| CSS variable | Description |
|---|---|
| --hero-stage-shift | Vertical arrival distance (default 1.75rem); negative drops the part in from above. |
| --hero-stage-drift | Sideways arrival distance; negative arrives from the left. |
| --hero-stage-tilt | The lean the part arrives with and settles out of. |
HeroStageReveal
A band of late copy (eyebrow, lede, CTAs, or a HeroTitle that must show at rest), placed on the vertical axis only; it centres its children. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| index | number | 0 | Place in the stagger: 0 lands first, each step a little further into the scroll. Number parts in landing order, not DOM order. |
| CSS variable | Description |
|---|---|
| --hero-stage-shift | Vertical arrival distance (default 1.75rem); negative drops the part in from above. |
| --hero-stage-drift | Sideways arrival distance; negative arrives from the left. |
| --hero-stage-tilt | The lean the part arrives with and settles out of. |
HeroSpread
The editorial spread, composed instead of HeroContent inside a split hero: a picture half and a panel half, stacked below lg. Forwards all native <div> props.
HeroSpreadMedia
The picture half; covers and crops a plain <img> or <video>. Put it first so it leads on a phone; forwards all native <div> props.
HeroSpreadPanel
The inked half; give it exactly two groups and it pushes the first to the top, the second to the foot. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| tone | "brand" | "ink" | "ink" | A slab pinned dark on every theme, or the raw accent. Lead with variant="neutral" over ink, Button tone="onMedia" over brand. |
HeroSpreadIndex
The chapter mark ("01") in the panel's outer corner, hidden from assistive tech. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| tone | "brand" | "ink" | "ink" | The panel tone it sits on, so the chip inverts with it. |
HeroStatement
The section's <h1> when the hero is one sentence, used instead of HeroTitle: display scale on a ~36-character measure. Forwards all native <h1> props.
HeroSignoff
A letter's closing under the statement, one line per child; a Signature can be its last line. Forwards all native <div> props.
HeroMosaic
The picture band across the top of a hero: a twelve-track grid of HeroFrames (two tracks while stacked) gated by viewport breakpoints. Forwards all native <div> props.
HeroFrame
One rounded, cropping picture frame with a hairline over the photograph; shape it with className and drop a plain <img> in. Drifts in on mount; forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| tone | "color" | "mono" | "color" | mono drops the colour so a wall of photographs reads as one. |
| delay | number | 0 | Milliseconds before it drifts in. |
