VideoHero

Full-viewport hero with background video

stable v1.0.1

Preview, desktop

A real render at 100svh inside the iframe. The strip at the bottom is the scroll cue's destination. Two-column composition, 55% / 40%.

Preview, mobile

At ≤767px: a single column, anchored to the top, scroll cue in flow below the text.

Behavior

StateBehavior
DefaultVideo on autoplay, muted, loop, playsinline
prefers-reduced-motionPauses the video, shows the poster
Scroll cue (with Lenis)Smooth-scrolls to the next sibling via window.__lenis
Scroll cue (without Lenis)Native fallback scrollIntoView({ behavior: 'smooth' })
No JSThe native autoplay muted loop attributes still apply

Tokens

TokenDefaultRole
--text-display80px / 40px mobileTitle size; a Major Third step above --text-h1 (64 × 1.25)
--hero-text#FFFFFFText color over the media
--hero-overlayrgba(0,0,0,0.2)Scrim over the media for contrast

Constraints

  • Video always muted (a precondition for autoplay on mobile) and decorative: no controls, no PiP.
  • Poster with alt="": a visual fallback, not content.
  • The 55% / 40% grid is a system default, not a prop.
  • No hardcoded colors or sizes: title via --text-display, text via --hero-text, scrim via --hero-overlay.

Decisions

DecisionWhy
Title with --text-display (80px = 64 × 1.25)The hero already rendered at 80px, exactly the next Major Third step above --text-h1, unnamed. An on-ratio step already in use gets a token; it is not an off-scale exception. Dropping to 64px was ruled out (it distorts a verified composition); so was leaving it hardcoded (an untokened value in the most visible component).
--hero-text tokenized, white by default, overridableThe original embedded #FFFFFF in the component: a site decision inside a system primitive. As a token it keeps white by default and allows re-theming without forking.
--hero-overlay tokenized, default rgba(0,0,0,0.2)The scrim's opacity is a contrast decision that depends on the footage. Tokenizing it lets legibility be tuned without touching the component.
55% / 40% grid as a system default, not a propThe asymmetric split is the component: a dominant title column, a secondary highlight column. Varying it doesn't theme it, it changes what the hero is. A prop would invite layouts it isn't designed to hold.
scrollLabel as a prop, Spanish by defaultThe cue's aria-label was hardcoded in Spanish, a site assumption. A prop with a Spanish default leaves consumers untouched and lets another locale pass its own label.
Optional Lenis, native fallbackThe system doesn't declare a scroll library. The cue improves with Lenis if it is there and degrades to native smooth scroll; the component carries no hidden dependency.