VideoHero
Full-viewport hero with background video
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
| State | Behavior |
|---|---|
| Default | Video on autoplay, muted, loop, playsinline |
| prefers-reduced-motion | Pauses 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 JS | The native autoplay muted loop attributes still apply |
Tokens
| Token | Default | Role |
|---|---|---|
| --text-display | 80px / 40px mobile | Title size; a Major Third step above --text-h1 (64 × 1.25) |
| --hero-text | #FFFFFF | Text color over the media |
| --hero-overlay | rgba(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
| Decision | Why |
|---|---|
| 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, overridable | The 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 prop | The 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 default | The 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 fallback | The 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. |