Skip to content

Motion

Scroll-driven motion that respects reduced-motion preferences. cui-scroll-stage publishes focus and offset as CSS custom properties; cui-reveal layers entry animations on top.

Scroll stage

cui-scroll-stage is a sticky 3D track that publishes its focus as CSS custom properties: --cui-stage-focus (0 → 1, peaks when centered), --cui-stage-offset (-1 → 1, signed distance from center after the hold band), and --cui-stage-active (0/1). Children animate with vanilla CSS by reading those values.

Scroll through the three stages below. Each applies a different transform recipe to the same published variables. Stages with a tone attribute write data-cui-tone to a scoped target while focused; the rig below is marked data-cui-tone-root so the tint stays inside it instead of bleeding to the whole page.

Stage 01 · effect="wheel" · tone="dawn"

Z-translate + rotateY

This card reads --cui-stage-focus and --cui-stage-offset and turns them into a depth-and-rotation move. The stage element holds focus through the hold band so the panel can settle visually before scrolling on.

transform:
  translate3d(0, 0, calc(var(--cui-stage-focus) * 220px - 320px))
  rotateY(calc(var(--cui-stage-offset) * 35deg));

Stage 02 · effect="slide" · tone="noon"

Signed offset slide

Same publisher, different recipe. Here the panel uses the signed --cui-stage-offset for a horizontal slide that flips direction either side of center. Notice how the page background tinted as this stage took focus.

transform: translate3d(calc(var(--cui-stage-offset) * -40%), 0, 0);
opacity: calc(0.3 + var(--cui-stage-focus) * 0.7);

Stage 03 · effect="scale" · tone="dusk"

Scale + blur

One last recipe: a scale-up paired with a defocus blur that clears as focus approaches 1. None of these stages know about each other's transforms — they just expose state.

transform: scale(calc(0.72 + var(--cui-stage-focus) * 0.28));
filter: blur(calc((1 - var(--cui-stage-focus)) * 8px));
API reference
Attribute / variable Purpose
focus-bias (-1..1) Shift what counts as "centered" in viewport space.
hold (0..1) Dwell band where focus stays at 1 before falling off.
threshold (0..1) Focus level at which --cui-stage-active flips to 1.
sticky-top Top offset for the sticky inner track (match your navbar height).
min-block-size Scroll runway length. Longer = slower wheel.
tone Key written to data-cui-tone on the resolved target while this stage is most focused.
tone-target Selector for where the tone is written. Defaults to the nearest [data-cui-tone-root] ancestor, falling back to the stage element itself. Use "document" to opt into the old page-wide behavior.
--cui-stage-focus Published 0..1. Peaks when stage is centered.
--cui-stage-offset Published -1..1. Signed distance from center after hold.
--cui-stage-active Published 0 or 1. Mirror of the .is-active class.

Reveal

cui-reveal wraps content that should fade and slide into place as its ancestor stage becomes focused. Direction via from, timing via delay, travel distance via distance. It's a pure-CSS element — it reads --cui-stage-focus from its surrounding stage and interpolates. Outside a stage, reveals stay fully visible.

from="up" delay 40ms
from="down" delay 140ms
from="left" delay 240ms
from="right" delay 340ms
from="fade" delay 440ms
distance="4rem" longer travel
API reference
Attribute Purpose
from up · down · left · right · fade
delay Transition delay. Unitless numbers are treated as ms.
distance Travel magnitude. Unitless numbers are treated as px.
threshold Focus level at which the reveal starts. Defaults to 0.6.