WebberUI

Depth Mask Hero

A lock-screen depth hero — a cut-out subject partially occludes the oversized headline, and on scroll the subject, headline, and background separate at different rates to produce real depth.

Loading preview…
npx shadcn@latest add https://webberui.com/r/depth-mask-hero.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

1
20
<DepthMaskHero />

Installation

npx shadcn@latest add https://webberui.com/r/depth-mask-hero.json

Or, once registries are configured in components.json, install it as @webberui/depth-mask-hero.

Usage

import { DepthMaskHero } from "@/components/ui/depth-mask-hero";

<DepthMaskHero
  title="OUTSIDE"
  kicker="Summer feature"
  date="Monday, July 13"
  time="9:41"
  subject="/people/hiker.png"
  subjectAlt="A hiker"
>
  <section className="px-6 py-16">The body copy starts here…</section>
</DepthMaskHero>

When no subject is provided, a built-in neutral-toned figure silhouette is rendered, so you can show off the masked composition without any external asset. The hero pins itself with position: sticky and only lets the children body copy come up once you have scrolled past it; if it scrolls inside a nested overflow container, remember to pass that container's ref to container.

Props

PropTypeDefaultDescription
titleReact.ReactNodeMain headline, set at a large size and partially occluded by the subject
titleAs"h1" | "h2" | "h3""h1"Semantic tag for the headline, to avoid clashing with the page title
kickerReact.ReactNodeKicker (small label) shown above the headline
subjectstringURL of the cut-out subject image (a transparent PNG is recommended)
subjectAltstringAlt text for the subject; treated as decorative when not provided
backgroundstringBackground image URL; falls back to a neutral gradient when not provided
timeReact.ReactNodeLock-screen time text, for example "9:41"
dateReact.ReactNodeLock-screen date text, shown above the time
heightstring"100svh"Height of the hero panel (any CSS length)
scrollDistancestring"80vh"Extra scroll distance needed for the depth separation
stickyTopnumber0Pinned position of the panel (px)
depthnumber1Offset multiplier for each layer's depth separation — larger makes the layering more pronounced
pointerParallaxbooleantrueWhether pointer parallax is enabled
parallaxStrengthnumber20Maximum offset of the pointer parallax (px)
springConfigSpringOptions{ stiffness: 140, damping: 20, mass: 0.4 }Spring parameters for the parallax spring back
containerReact.RefObject<HTMLElement | null>Ref of the nested scroll container; defaults to the window as scroll container
childrenReact.ReactNodeBody copy revealed after scrolling past the hero
heroClassNamestringClass applied to the hero panel
titleClassNamestringClass applied to the headline

How it works

  • Three-layer depth separation: the background (z-0) moves the least and against the pointer, the headline (z-10) sits in the middle, and the cut-out subject (z-20) moves the most and with the pointer; on scroll the three layers move at different rates, and that rate difference is the depth.
  • Depth mask: when the subject is a cut-out image (a transparent PNG), the transparent areas naturally expose the headline behind it while the opaque areas cover it, producing the lock-screen effect of "text wrapping behind the subject" without any extra mask asset.
  • Lock-screen details: time and date are shown pinned at the top in a lock-screen style, and fade out first as you scroll.
  • Container-query layout: the panel establishes a containment context with container-type: size, and the headline and details all use cqw units, so they scale proportionally at any panel size.
  • Timer / listener cleanup: pointer parallax is driven by motion's MotionValue and useSpring, and scroll progress is bound through useScroll; events are only registered on pointermove / pointerleave, and React removes them on unmount.

Accessibility

  • When the user has "reduce motion" enabled at the system level, scroll parallax and pointer parallax are disabled and the hero renders as static stacked layers, with the layout structure unchanged.
  • The headline is output as a real heading tag (titleAs), h1 by default; on a page that already has a main heading you can drop it to h2 / h3.
  • When subjectAlt is provided, the subject image is announced as a meaningful image; when it is not, the image is aria-hidden and treated as decorative only.
  • Purely visual layers such as the background, vignette, and lock-screen time are all marked aria-hidden, so they do not interfere with the reading order for assistive technology.

On this page