Skip to main content

Pinned scenes

A pinned (or "sticky") scroll scene is a tall section with content that stays fixed on screen while the section scrolls past behind it — the classic "scroll-driven story" pattern. It's the foundation nearly every advanced scroll recipe in this site builds on (see Advanced recipes).

Two things make a pinned scene: a pair of Fancy settings you toggle on in the panel ("I am parent container" and "Get parent", below), and the actual sticky layout itself — the tall section and its pinned stage — which is built with ordinary CSS (position: sticky, a height), the same way you'd build it without the plugin. The CSS for that part is covered further down this page, for whoever is setting up the layout.

See it live: the Section card stack and Stacked cards templates.

I am parent / Get parent​

Inside a pinned stage, the pinned element itself never moves — so if it tried to measure its own scroll progress, that progress would freeze the moment it became pinned. The fix is to measure a different box: the whole tall section around it, which keeps moving the entire time.

SettingGoes onWhat it does
I am parent containerThe tall, outer section — the box everyone should measure againstMarks it as a measuring reference
Get parentEvery animated element inside the pinned stageMeasures against the nearest "I am parent" ancestor instead of itself

With no "I am parent" ancestor above it, "Get parent" silently falls back to measuring the element's direct parent instead — which, inside a pinned stage, is also stuck. If animations freeze the moment the stage pins, this is almost always why: check that "I am parent" is on the outer section and "Get parent" is on every animated element inside the stage.

Structure​

section "I am parent" · height: a multiple of 100vh · padding: 0 · overflow-x reset
└─ stage position: sticky · top: 0 · at least one screen tall
└─ content "Get parent" + scroll trigger rows
  • The section needs its horizontal overflow reset (see the Safari pitfall below) and its own padding removed — the content's side gutter has to move to the stage (and to every other non-stage content of the section) instead, including on phones.
  • A sticky element moves relative to its own parent, not the whole section — so if the stage sits inside an extra wrapper that's shorter than the section, it releases earlier than you'd expect.

The pinned window​

A scene that's k screens tall (e.g. 300vh = 3 screens) is pinned from scroll progress 1/(k+1) to k/(k+1). Every animation row inside the scene should be planned to fall inside that window:

Scene heightPinned windowRows on the 5% grid
200vh33% – 67%35 – 65
300vh25% – 75%25 – 75
400vh20% – 80%20 – 80
500vh17% – 83%20 – 80

A row that starts before the window plays while the scene is still scrolling into place (fine for an arriving parallax effect); one that ends after it plays while the stage is being released and already sliding away, which usually looks like it finished too late.

As the very first section of a page (a hero), the scene is already sitting at its pinned-start value the moment the page loads — so rows inside the window start on the visitor's very first scroll, same as anywhere else. Rows placed before that value are shown already partly played. For timings that don't land on the 5% grid, see Scroll math & custom CSS.

Keep hero text visible and static

If the pinned scene is your page's hero, keep its headline and first line of copy static and visible from load rather than animating them in. That text is very likely your page's largest contentful paint — if it starts hidden (an entrance, or a Fade row whose window hasn't started yet), you delay LCP and show an empty-looking screen on a slow connection. Animate the stage's visuals instead and let the words stay put.

A height that still works without JavaScript​

A fixed scene height (say, height: 400vh) can cut off the stage's content on short screens when there's no JavaScript running — in the Bricks/Etch canvas with the preview off, say, or with JavaScript blocked. Make the height itself depend on the animation existing, using a Custom row with no window on the section:

%root% {
--scene-h: calc(var(--fa-scroll--custom) * 0vh + 400vh);
height: var(--scene-h, auto);
}

Without the scroll script, --fa-scroll--custom doesn't exist, which makes --scene-h invalid, which falls back to height: auto — so the stage simply isn't pinned and its content flows normally, with nothing cut off.

  • Section: "I am parent container" in the ScrollTrigger Animations panel, padding set to 0 in the Spacing control, and in its custom CSS: %root% { overflow-x: initial; }.
  • Stage: either the CSS above, or the native controls (Position: Sticky, Top: 0, Min height: 100vh, Overflow: hidden).
  • Every animated element inside: "Get parent", plus its own ScrollTrigger rows with Start/End inside the pinned window.
  • Never put a gradient overlay directly on the sticky stage at any breakpoint — Bricks then outputs position: relative on that same element, which kills the sticky behaviour. Use a separate absolute "shade" element instead.
  • Remember: nothing animates in the canvas until the toolbar's FA toggle is on (it's off every time you reopen the builder).

Common problems​

  • The stage jumps or stutters in Safari / on iPhone. The plugin sets overflow-x: clip on body and section, and Safari can make a sticky child jump when that's active. Reset overflow-x: initial on every section that holds a sticky stage. This doesn't reproduce in automated Chromium testing — check it in real Safari.
  • Animations freeze the moment the stage becomes pinned. The animated elements (or the section itself) are measuring themselves instead of the section. Add "I am parent" to the section and "Get parent" to every animated element inside it.
  • Content is cut off on short screens, or in the builder. The scene has a fixed height and the stage has a fixed height: 100vh instead of a min-height. Use the height-with-fallback CSS pattern above, and min-height (not height) on the stage.
  • The scene collapses to a narrow column, or something with container-type inside it measures 0px. In Bricks, a section is a centred flex column, so a plain div inside it shrinks to the width of its content. Give the scene (and any wrapper between the section and the stage) an explicit width: 100%.
  • Text touches the edge of the screen on phones. The section's own padding was removed for the sticky stage, and nothing picked up that gutter in its place. Add padding-inline (e.g. clamp(1rem, 5vw, 2rem)) to the stage and to every other direct content of the section, at every breakpoint.
  • An animation plays after the stage has already been released, or finishes visibly early. Its window falls outside the pinned stretch for that scene height — move the row, or make the scene taller.
  • A horizontal scrollbar appears on the page. Something inside the stage is moving sideways beyond its edges — add overflow: hidden to the stage itself.