Page Sections

Browser Frame Hero

A hero with one large browser-style device frame filling the right side, which straightens and scales up from a slight tilt as the page scrolls, holding any product screen you put inside.

A landing-page hero: an eyebrow, title, subtitle and two CTAs on the left, and one large device frame filling the right side — either a browser (three dots + URL pill) or a laptop (screen + base). As the section enters the viewport, the frame turns from a slight backward lean and side angle to face the viewer and scales up to full size with the scroll, finishing within the first 40% of scroll progress and holding there; the soft shadow under it fades along with the angle. Put your own screenshot or component inside; without one, it draws a placeholder dashboard.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add browser-frame-hero.en

New project or installation trouble? Start here →

Playground

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

12
120
600
Active edited usageDefault props
<BrowserFrameHero/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/browser-frame-hero.en.json

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

Usage

import { BrowserFrameHero } from "@/components/ui/browser-frame-hero";

<BrowserFrameHero
  eyebrow="Lanyun Ops"
  title="One screen to check before you open the shop"
  subtitle="Revenue, orders and repeat rate on a single screen."
  primaryAction={{ label: "Try it free for 14 days", href: "/signup" }}
  secondaryAction={{ label: "Take the tour", href: "/tour" }}
  url="app.example.com"
  frameLabel="Ops dashboard screenshot"
>
  <img src="/screens/dashboard.png" alt="" className="h-full w-full object-cover" />
</BrowserFrameHero>

Inside a nested scroll container

By default the component tracks window scroll. When it sits inside a fixed-height overflow-y-auto container, pass that container's ref to container. The frame's tilt and scale follow how far the hero has scrolled through the container.

const scrollerRef = React.useRef<HTMLDivElement>(null);

<div ref={scrollerRef} className="h-[400px] overflow-y-auto">
  <BrowserFrameHero container={scrollerRef} />
</div>

When the window itself is the scroll container, container can be omitted and behavior is unchanged.

Props

PropTypeDefaultDescription
titleReactNode—Main title (required)
eyebrowReactNode—Small label above the title
subtitleReactNode—Supporting text below the title
primaryActionBrowserFrameHeroAction—Primary button with the accent background
secondaryActionBrowserFrameHeroAction—Secondary outlined button
as"h1" | "h2""h1"Heading level
minHeightnumber560Minimum section height (px)
variant"browser" | "laptop""browser"Device frame style: browser or laptop
urlstring"app.example.com"Text inside the browser URL pill
tiltnumber12Initial maximum tilt (deg); rotateX goes from this value to 0, rotateY from minus 2/3 of it
bleedbooleantrueFrom md up, the frame extends past the container's right edge and is clipped by the section
imagesBrowserFrameHeroImage[]—Images for the frame; only the first is used, and children take priority
frameLabelstring"產品儀表板示意圖" (Product dashboard illustration) with the placeholderAccessible name of the frame figure
childrenReactNodePlaceholder dashboardFrame content (screenshot or component); it provides its own semantics
containerRefObject<HTMLElement>—Ref of the nested scroll container; omit to use the window as the scroll container
classNamestring—Class merged into the root section

BrowserFrameHeroAction

FieldTypeDefaultDescription
labelstring—Button text
hrefstring—Renders an a link when provided, otherwise a type="button"
onClick() => void—Click callback

BrowserFrameHeroImage

FieldTypeDefaultDescription
srcstring—Image URL; omit it to draw the placeholder dashboard
altstring—Alt text, used as the frame's accessible name when frameLabel is not set

How it works

  • Scroll-driven: useScroll targets the section itself (offset: ["start end", "end start"]). Between progress 0 and 0.4, rotateX goes from tilt to 0, rotateY from -tilt × 2/3 to 0, scale from 0.94 to 1 and y from 40px to 0, then holds the final state.
  • Smoothing: all four values pass through useSpring (damping 24) with stiffness read from --wb-browser-frame-hero-stiffness (default 120). The variable is read once on mount, so changing it at runtime needs a remount.
  • The outer wrapper sets perspective: 1400px, and the frame rotates around its bottom center.
  • Entrance: eyebrow, title, subtitle and CTAs rise 16px and fade in one after another, 80ms apart; the frame fades in and rises 24px. Both use --wb-browser-frame-hero-duration (default 600ms) with cubic-bezier(0.22,1,0.36,1).
  • Accent: --wb-browser-frame-hero-accent (default #0d9488 in light, #2dd4bf in dark), used by the eyebrow, primary button, focus ring and the placeholder dashboard's highlighted figures.
  • From md up the layout is a 4 / 8 two-column grid; on narrow screens the frame moves below the title.

Accessibility

  • The root is a section with aria-labelledby pointing at the title; the heading level comes from as.
  • The frame is a figure. The placeholder dashboard is aria-hidden and the figure gets its name from aria-label. When you pass children, your content provides the semantics; add frameLabel if it needs a name.
  • The three dots, the shadow under the frame and the laptop base are decorative and hidden from assistive tech.
  • CTAs are a or type="button" with a clear focus-visible ring.
  • With "reduce motion" turned on, the scroll-driven tilt and scale stay at the final state (facing forward, full size) and the entrance becomes a 150ms fade; the entrance media query applies from the first paint.