Sticky Scroll Story

A visual stays pinned while the copy advances beside it.

← All motions

Sticky Scroll Story

The structure behind most brand story pages: one panel stays fixed while three or four beats scroll past it. It looks like a JavaScript scroll hijack, but the pinning is one CSS line. What actually needs care is deciding which beat is "current" — and that is where AI output usually breaks.

trigger: scroll position: sticky IntersectionObserver

Live demo

01

Pin the panel

One column is position: sticky. It stops at the top of the scrollport and waits while the other column keeps moving.

Advance the copy

The right column is just normal blocks in normal flow. No translate, no scroll listener — the page scroll does all the work.

Watch the centre band

An observer with a negative rootMargin fires only for the beat crossing the middle of the panel. That is how the pinned side knows what to show.

The prompt

Build a sticky scroll story section: a two-column layout where the left panel stays pinned while three text beats scroll past on the right.

Layout:
- A scroll container with a fixed height (e.g. 320px) and overflow-y: auto. Inside, a CSS grid with two columns: a narrow stage column and a wider copy column.
- The stage column uses position: sticky; top: 0; align-self: start; and an explicit height equal to the container height. Do not use position: fixed — it would escape the container and break on any layout change.
- The copy column contains three or more blocks with normal document flow. Do not use transforms on them.

Interaction:
- Track the "current" beat with an IntersectionObserver whose root is the scroll container and whose rootMargin is '-45% 0px -45% 0px'. That shrinks the observation area to a thin band across the middle, so exactly one beat is active at a time.
- On activation, update the pinned panel (a step number and a colour). Do not rebuild the panel's DOM on every change — update text and a CSS custom property only.

Constraints:
- No scroll event listener and no scroll position maths; the observer does the work.
- The copy must remain selectable real text, in normal flow, so it stays crawlable.
- Respect prefers-reduced-motion: keep the layout, but skip any transition on the pinned panel.
- On viewports under 600px, stack the columns vertically and drop the sticky behaviour — a pinned panel on a phone is a usability problem, not a flourish.

What actually makes it work

Compatibility and performance

AspectDetail
position: stickyUniversal in modern browsers. Requires no ancestor with overflow: hidden between the element and the scrollport.
IntersectionObserverUniversal. Fires off the main thread; a handful of callbacks per scroll session.
CostNear zero. The pinning is compositor work; only the state change touches the DOM.

The most common failure in the wild is a parent with overflow: hidden somewhere up the tree — the sticky element silently stops working and there is no error to find. That is worth checking first when a sticky section "just doesn't stick".

When not to use it

Don't pin content that has to be read in one pass. Scrollytelling delays information behind scroll distance. For pricing, specs or docs, that is friction for no gain.

Don't stack two pinned sections. Two panels fighting for the same scrollport reads as a broken page, and on shorter viewports they overlap.

Don't ship it on mobile without the stacking fallback. A pinned visual plus a short copy column leaves a phone showing one line of text at a time.