Skip to content

Parallax

Add depth by moving, scaling, rotating or fading an element as the page scrolls.

Use it to give a marketing scene depth: layered product shots, floating cards around a headline, a backdrop that moves slower than the content. Keep it off anything people read while they scroll, like articles, lists and app screens. Drifting text is tiring, and large movement can make some people feel unwell.

Installation

npm i @youj/motion

import { Parallax } from '@youj/motion'

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Usage

Give it an output range for each property you want to move. With no container, it follows the page's scroll.

import { Parallax } from '@youj/motion'

export function Hero() {
  return (
    <section className="relative">
      <Parallax y={[-60, 60]} className="absolute inset-0">
        <HeroBackdrop />
      </Parallax>
      <Parallax y={[40, -40]} rotate={[-3, 2]}>
        <ProductShot />
      </Parallax>
    </section>
  )
}

x and y are in pixels, rotate in degrees, and scale and opacity are plain numbers. A property you don't pass stays at rest.

Examples

Layers

Depth comes from contrast between speeds. Give far layers small ranges and near layers large ones, and let the ranges run against the scroll, so near things seem to rise past far ones.

Reference

Propsinput, x, y… (10)

Props

Parallax props
PropType
inputnumber[]

Scroll progress stops (0..1 of the element's pass through offset).

default [0, 1]

[0, 1]
xnumber[]

Output ranges, one value per input stop. Translate values are px.

–
ynumber[]–
scalenumber[]–
rotatenumber[]–
opacitynumber[]–
offsetScrollOffset

Scroll offsets defining progress 0 and 1.

default ['start end', 'end start']

['start end', 'end start']
containerRefObject<HTMLElement | null>–
springSpringToken | false

Spring-smooth the scroll input. Pass false to track it exactly.

default 'smooth'

'smooth'
styleMotionStyle–
Other props pass through: extends HTMLMotionProps<'div'>.
MotionScroll input, mapping · reduced motion

Motion

Motion tokens
MomentTokens
Scroll inputsmooths the scroll progress; spring={false} tracks it exactly
spring.smooth

0.36s, bounce 0

0.36s, bounce 0
Mappinglinear between input stops, clamped at the ends
no token

no easing

no easing

Reduced motion

The element sits at rest: no translate, scale or rotation, and full opacity.

The spring gives the layers a moment of inertia, so a fast fling settles instead of snapping. Every output is a transform or an opacity, so scrolling never triggers layout.

Reduced motion

Every layer sits still at its resting position, fully opaque, as if the scene were a static composition. Scrolling moves the page and nothing else.

An element at rest isn't always the right static design. A glow that marks the card in focus would light every card at once, so the release list leaves the glows out when useYouj().reduced is true.

AccessibilityKeyboard, focus

Accessibility

  • Parallax only transforms. The element keeps its place in the document and the reading order.
  • Movement tied to scroll is a known trigger for vestibular disorders. Reduced motion turns it off completely, and keeping ranges modest helps everyone else.
  • Don't fade text people need below a readable contrast. In the release list, the text stays at full contrast in every position, and only a decorative glow fades.
  • A contained scene needs a focusable, named scroll container to be reachable from the keyboard.