Installation
Usage
import { PhoneScrollHero } from "@/components/ruixen/phone-scroll-hero";
export default function Page() {
return (
<PhoneScrollHero
titleComponent={
<h1 className="text-4xl font-semibold text-foreground">
Every account,
<br />
<span className="mt-1 block text-5xl font-bold leading-none md:text-[6rem]">
in your pocket
</span>
</h1>
}
>
<video
src="/app-screen.mp4"
autoPlay
muted
loop
playsInline
className="h-full w-full object-cover"
/>
</PhoneScrollHero>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
titleComponent | ReactNode | Required | Headline block. Renders behind the phone so the phone can climb over it. |
children | ReactNode | - | Screen content. Falls back to a themed placeholder. |
overlap | number | 20 | How far the phone climbs into the title, as a % of its own height. |
tilt | number | 22 | Backward tilt of the phone in degrees at the start of the scroll. |
className | string | - | Extra classes on the section — use h-[…] to change how long the scroll runs. |
Features
- Tilt that resolves on scroll — the phone starts rotated back on the X axis inside a
1200pxperspective and straightens to flat as the section scrolls through, so the device appears to stand up. - Overlaps the headline — the phone starts tucked under the title with a negative margin, lifts faster than the title (which only drifts
-24px) and sits on a higherz-index, so the gap closes and the device ends up covering the lower part of the letters. - Overlap scales itself — the lift is a percentage of the phone's own height rather than a pixel value, so the effect is proportionally identical on a
13remphone and a17remone. No resize listener, noisMobilestate. - Works inside any scroller — progress is measured against the section's own
ownerDocument.defaultView, so it responds to the real scroll container (including this iframe preview), not just the top-level window. - Reduced-motion fallback — with
prefers-reduced-motionset, the phone renders flat and untransformed below the title, with no scroll listener attached. - Bring your own screen —
childrenrenders inside the screen cutout, so drop in a<video>, a screenshot, or an inline mock built from your own components. - Theme adaptive — the chassis, buttons, screen and placeholder are all shadcn tokens (
bg-muted,bg-background,border-border,bg-muted-foreground/30,text-muted-foreground), so the device follows your theme in light and dark. The camera island is the one deliberate exception: it stays black, because it is a hole in the glass, not a themed surface. - Semantics and a11y — renders as a labelled
<section>, every chassis decoration isaria-hidden, nothing is keyboard-focusable that shouldn't be, and the motion is gated behindprefers-reduced-motion.
Tuning the scroll length
The section is h-[60rem] md:h-[80rem] by default — the tilt resolves over the
distance between the section entering and leaving the viewport. Pass a taller
height for a slower, more cinematic straighten:
<PhoneScrollHero className="h-[80rem] md:h-[110rem]" titleComponent={…}>
…
</PhoneScrollHero>
