Media

Device Stage 3D

A pure-CSS 3D stage for phones, tablets and laptops with five arrangements: pointer tilt, scroll orbit, Y-axis flip, a fanned lineup of devices and a scroll-driven macro pull-back; screens are live DOM.

This is a WebberUI Pro component

The install command below needs a licence to run; without one it returns 401.

How to install Pro components →See the plans →

The classic product-page shot: put your app inside a phone or a laptop and let it move on the page. Every phone, tablet and laptop frame here is drawn in CSS (rounded corners, bezels, the pill-shaped island, side buttons, a keyboard deck) with no images, and the screen holds live DOM — text is selectable, buttons are clickable and screen readers can read it. Five arrangements are available: pointer tilt, scroll orbit, Y-axis flip (the back shows a second screen or a drawn back shell), a fanned lineup of three devices, and a scroll-driven pull-back from a macro close-up to the full view.

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 "https://webberui.com/r/device-stage-3d.en.json?t=<install token>"

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.

8
3
1400
120
Active edited usageDefault props
<DeviceStage3D/>

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/device-stage-3d.en.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/device-stage-3d.

Usage

import { DeviceStage3D } from "@/components/ui/device-stage-3d";

<DeviceStage3D
  device="phone"
  mode="flip"
  screens={[<HomeScreen key="home" />, <SettingsScreen key="settings" />]}
  caption={<p>點擊手機看設定頁</p>}
  maxTilt={8}
/>;

The caption above reads 點擊手機看設定頁 ("click the phone to see the settings screen").

orbit and macro are scroll-driven. When the stage sits inside a scrollable element, pass that element's ref to container:

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

<div ref={scrollerRef} className="h-[480px] overflow-y-auto">
  <div className="h-[320px]" />
  <DeviceStage3D mode="macro" container={scrollerRef} screens={[<HomeScreen key="home" />]} />
  <div className="h-[320px]" />
</div>;

Props

PropTypeDefaultDescription
screensReact.ReactNode[]—Screen content (1–3); flip uses the 2nd for the back, lineup fills three devices in order and repeats when short
device"phone" | "tablet" | "laptop""phone"Device shape
mode"tilt" | "orbit" | "flip" | "lineup" | "macro""tilt"Arrangement
captionReact.ReactNode—Side caption; in flip mode it slides in again 120ms after every flip
flippedboolean—Controlled flipped state
defaultFlippedbooleanfalseInitial flipped state in uncontrolled mode
onFlippedChange(flipped: boolean) => void—Called when the flipped state changes
clickToFlipbooleantrueIn flip mode, clicking the device flips it, and a keyboard flip button is provided
perspectivenumber1400Stage perspective distance (px)
maxTiltnumber8Maximum tilt angle in tilt mode (degrees)
depthBlurnumber3Starting blur (px) for out-of-focus layers in macro mode; 2–4 works best
flipStiffnessnumber120Flip spring stiffness (damping is fixed at 20)
gyrobooleanfalseDrive tilt from the gyroscope on touch devices
containerReact.RefObject<HTMLElement | null>—Scroll container that drives orbit/macro progress; defaults to the window
screenClassNamestring—Passed to every screen area
classNamestring—Passed to the outermost container

The DeviceStage3DDevice, DeviceStage3DMode and DeviceStage3DProps types are also exported by name.

CSS variables

FieldTypeDefaultDescription
--wb-ds3d-framecolor#171717 (dark mode #262626)Frame and side-button color
--wb-ds3d-highlightcolorrgba(255,255,255,0.14)1px inner highlight on the frame
--wb-ds3d-backcolor#e5e5e5 (dark mode #525252)Starting color of the back-shell gradient
--wb-ds3d-deckcolor#d4d4d4 (dark mode #404040)Laptop keyboard deck color
--wb-ds3d-glowcolorrgba(115,115,115,0.16)Soft glow behind the stage

How it works

  • tilt: the pointer position relative to the stage center becomes −1 to 1 and maps to rotateY ±maxTilt and rotateX ∓maxTilt, smoothed by a stiffness 150 / damping 18 spring; leaving the stage returns it to center. Touch devices (pointer: coarse) do not follow by default; with gyro on it reads deviceorientation instead, and iOS asks for permission after the first touch
  • orbit: scroll progress 0 to 1 maps to rotateY −30° to 30°, and translateZ peaks at 40px in the middle
  • flip: rotateY 0 to 180°, screens[0] on the front and screens[1] on the back; with a single screen the back is drawn as a shell (camera module and matte finish). Clicking buttons or links inside the screen does not flip
  • lineup: three devices fan out — the outer two turn inward 18°, shift out 60%, step back 80px and shrink to 0.9; when the stage enters the viewport they spring up from below 0.12s apart
  • macro: while scrolling until the stage is centered in the viewport, the group pulls back from 1.6× to 1× and its 12% offset returns to zero; the background decoration and side devices (when there is more than one screen) go from depthBlur to sharp
  • laptop lid: the screen panel opens around its bottom edge from −90°; normally it springs open when entering the viewport, and in orbit/macro it is bound to the first 30% of scroll

Accessibility

  • The outer element is role="group" with aria-roledescription="device showcase"; the frame, side buttons, cameras and keyboard are aria-hidden decoration, and screen content stays readable and focusable
  • Flip mode provides a keyboard flip button (aria-pressed reflects whether the back is shown) that stays hidden until it receives Tab focus and responds to Enter/Space; the face turned away from the user is inert, so it cannot be tabbed into
  • Side devices in macro are decoration only and are set to aria-hidden and inert
  • When the user's system has reduce motion turned on, every mode stays facing front and ignores the pointer and scroll, lineup appears in place, the laptop lid is already open, and flip becomes an in-place cross-fade between the two faces