Layout Composition

Paper Layers Scene

A scene of 3–5 torn-edge paper layers with soft cut-paper shadows that parallax on scroll or cursor, for storytelling heroes and editorial layouts.

Stack 3 to 5 torn-edge paper sheets from back to front into a single scene: each sheet has its own colour, an irregular torn edge generated deterministically from a seed, a soft paper shadow that follows the torn shape, and optional paper-fibre grain. As the page scrolls or the cursor moves, sheets closer to the front shift further, giving a layered cut-paper parallax. Each layer can hold your own SVG drawings or text, and the top-most children slot is a normally readable heading area — a good fit for storytelling heroes and editorial layouts.

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 paper-layers-scene.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.

18
120
3
Active edited usageDefault props
<PaperLayersScene/>

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/paper-layers-scene.en.json

Or, once registries are configured in components.json, install it as @webberui/paper-layers-scene.

Usage

import { PaperLayersScene } from "@/components/ui/paper-layers-scene";

<PaperLayersScene
  mode="both"
  layers={[
    { id: "far", color: "#efe7da", height: "74%" },
    { id: "mid", color: "#e2d4bd", height: "56%" },
    { id: "near", color: "#c2ab8a", height: "36%", tornEdge: "both" },
    { id: "front", color: "#97805c", height: "18%", content: <MyGrassSvg /> },
  ]}
>
  <h2 className="pt-10 text-center text-3xl font-semibold">A Slow Reading Weekend</h2>
</PaperLayersScene>

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. When mode includes scroll, the layer parallax follows how far the scene has scrolled inside the container.

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

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

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

Props

PropTypeDefaultDescription
layersPaperLayer[]four default warm papersPaper layers stacked back to front; 3 to 5 is recommended
mode"scroll" | "pointer" | "both""both"What drives the parallax; on touch devices pointer is disabled and only scroll remains
intensitynumber18Cursor parallax strength (px): the max shift of a depth-1 layer when the cursor reaches the edge
rangenumber120Scroll parallax distance (px): total travel of a depth-1 layer across the whole scroll
stiffnessnumber120Cursor spring stiffness; higher follows more tightly
dampingnumber20Cursor spring damping; lower leaves more wobble when settling
seednumber3Torn-edge random seed; the same seed always yields the same edge
grainbooleantrueOverlay a paper-fibre texture (multiply 6%)
aspectRatiostring"16/9"Scene aspect ratio (CSS aspect-ratio)
tiltnumber0Max scene tilt following the cursor (degrees), capped at 3; 0 turns it off
childrenReactNode—Text area stacked on top; read normally by screen readers
containerRefObject<HTMLElement>—Ref of the nested scroll container; omit to use the window as the scroll container
classNamestring—Passed to the outermost container

PaperLayer

FieldTypeDefaultDescription
idstring—Unique layer identifier
colorstringwarm paper by layer orderPaper colour (#f5efe6, #e9dfcf, #d9c9b0, #c2ab8a, #a88d68)
tornEdge"top" | "bottom" | "both""top"Which edge is torn
depthnumber0.2 → 1 by layer orderDepth factor; larger moves more
heightstringdecreasing back to frontShare of the scene height as a percentage string, e.g. "45%"
contentReactNode—Content on the sheet (your own SVG or text), clipped by the torn edge too

How it works

  • The torn edge is a clip-path: polygon(...): a deterministic RNG seeded by seed and the layer index places 24 to 48 points along the edge, mixing a low-frequency undulation with small high-frequency jitter. No Math.random, so the shape matches between SSR and the client.
  • The shadow is two stacked drop-shadows on the outer element while the clip-path sits on the inner one, so the shadow follows the torn shape. Override the whole shadow string with the --wb-paper-layers-shadow CSS variable.
  • Scroll parallax tracks the scene itself with useScroll: layers rest at zero when the scene is centred in the viewport, and near layers rise faster than far ones as you scroll down.
  • Cursor parallax uses the scene centre as its origin and is spring-smoothed; when the cursor leaves, layers ease back to centre over --wb-paper-layers-ease-duration (default 600ms).
  • Every sheet bleeds past the scene on the left, right and bottom, so even large offsets never reveal a sheet's edge.

Accessibility

  • All paper layers are decorative and the whole group is aria-hidden; headings and text in children are unaffected and read normally by screen readers
  • Touch devices (no hover, coarse pointer) automatically disable cursor parallax and keep only scroll parallax
  • When the system "Reduce motion" setting is on, all sheets stay still while keeping the paper texture, torn edges and shadows