sift.All magazines · About
Design Observer · Week of June 28, 2026

Chrome 146 Ships Scroll-Triggered Animations: CSS Reclaims Interaction from JavaScript

Scroll-Triggered Animations Just Shipped. Now We Need to Agree on What to Call Them.

If you wanted opposing-direction scroll columns—outer columns scrolling up while the center scrolls down, items fading at boundaries—you reached for GSAP ScrollTrigger or wrote custom Intersection Observer logic. Today, that same pattern ships in production with `timeline-trigger: view()` and zero JavaScript. Chrome 146 has shipped scroll-triggered animations, making the feature available to developers.

Three-column layout, outer columns moving upward on scroll while the center moves down, choreographed card reveals, hero text splitting with stepped opacity transitions—all without touching the JavaScript main thread. The opposing-direction columns pattern—which the original developer described as starting from "silly ideas that eventually grow on you"—now runs on GPU-accelerated CSS, no library required. It respects reduced motion settings out of the box. And it works because Chrome shipped scroll-triggered animations in a browser for the first time.

The first major browser milestone in CSS's reclamation of interaction logic arrives precisely when the ecosystem can't agree on what to call anything.

But here's the counterintuitive tension: the first major browser milestone in CSS's reclamation of interaction logic from JavaScript arrives precisely when the ecosystem can't agree on what to call anything. Developers are saying one and meaning another, using one when needing another. Scroll-driven animations, scroll-triggered animations, container query scroll states, view transitions—four overlapping terms, four distinct behaviors, and production code conflating them into cargo-cult implementations that miss the architectural point entirely.

The confusion starts with the two load-bearing properties that sound almost identical but do opposite things. `animation-timeline: view()` creates a scroll-driven animation where animation progress synchronizes with scroll progress. Scroll forward, the animation moves forward. Scroll backward, it runs backward. Stop scrolling, the animation stops. There is no duration. The animation has no fixed length because it's tethered to scroll position, measuring how much an element is within the viewport and responding accordingly.

`timeline-trigger: --trigger view() entry 100% exit 0%` creates a scroll-triggered animation that waits for a threshold then plays a fixed-duration animation. The element crosses the trigger activation range—say, when it becomes fully visible—and the animation runs for its declared duration, say 300 milliseconds. It plays once, or plays forwards and backwards, or locks in after the first play depending on your `animation-trigger` settings, but the key is that it's decoupled from scroll progress. Think Intersection Observer API but for CSS animations.

The syntax distinction is subtle enough to be lethal. One uses `animation-timeline`, the other uses `timeline-trigger`. One has no duration, the other requires one. One measures intersection degree, the other waits for a threshold. Developers who've internalized the scroll-driven patterns from the past two years are now writing `animation-timeline: view()` when they need `timeline-trigger: view()`, expecting threshold-based behavior and getting scroll-synchronized progress instead. The card text reveals use view-timeline (not timeline-trigger) to flip --scroll-reveal from 0 to 1, which then drives opacity and translate transitions. That's scroll-triggered. If they'd used `animation-timeline`, the fade would stutter with every scroll event, synchronized to scroll progress rather than playing smoothly once the element crossed the threshold.

Comparison of scroll-driven versus scroll-triggered animation properties
Comparison of scroll-driven versus scroll-triggered animation properties

Per the Shopify Editions demo, the media carousel uses scroll-driven animations to play nice with scroll-snap and drive scaling effects. The hero text uses scroll state (container query scroll states) to flip visibility—visual characters are split with CSS delays, with choreographed, stepped opacity transitions and no DOM churn. Same page, two different patterns, two different properties, and if you swap them the whole thing falls apart.

· · ·

Now add container query scroll states, which live in the CSS Conditional Rules Module Level 5 draft and update styles when a container reaches some scroll condition. A sticky nav changes background and border-radius when `@container scroll-state(stuck: top)` evaluates to true. It's sorta like scroll-driven, sorta like scroll-triggered, but it's neither—it's a container query that responds to scroll-derived state rather than an animation at all. Developers who learned container queries for responsive layout are now seeing `scroll-state` and assuming it's related to `animation-timeline: scroll()` when it's actually a conditional styling hook with no animation timeline involved.

And then view transitions, which have nothing to do with scroll and nothing to do with `view()` despite the naming collision. The view transitions API animates state changes on the same page—radio button check states, element position morphs—or transitions between pages with crossfades or custom clip-path wipes. The video modals on Shopify Editions use view transitions for the video morph and a separate view-transition for the nav to ensure proper overlay behavior and avoid z-index collisions depending on scroll position. That's a same-document transition. It's not scroll-driven. It's not scroll-triggered. It's a separate API that happens to share the word "view" with `view()` in scroll animation syntax, and developers are conflating them in GitHub issues and Stack Overflow threads because the terminology collision makes it nearly impossible to search for the right documentation.

This isn't pedantry. The architectural stakes are real. Moving interaction logic from JavaScript to native CSS shifts work from the main thread to the GPU. The browser's engine optimizes performance under the hood, saving energy and processing power. The opposing-direction columns that required custom JavaScript now run as GPU-accelerated CSS with automatic reduced motion respect, no accessibility bolted on afterward. The scroll-driven carousel scales smoothly because the browser compositor handles the transform without blocking on JavaScript event listeners. The card reveals don't flash or stutter because they're not recalculating opacity on every scroll event—they play a fixed-duration animation once the threshold is crossed, and the browser schedules it efficiently.

But only if you use the right property. If you use `animation-timeline` when you need `timeline-trigger`, you get scroll-synchronized jank instead of smooth threshold-based playback. If you use `timeline-trigger` when you need `animation-timeline`, your scaling effect locks at the end of its duration instead of continuously responding to scroll. And if you're reaching for view transitions when you actually need scroll-driven animations, you're adding API surface area for state management when the browser could handle the whole thing as a declarative timeline.

The performance and accessibility wins are measurable, but they only materialize if the ecosystem stabilizes terminology and migration patterns before the confusion calcifies. Right now, developers are copy-pasting demos without understanding which property does what, shipping code that technically works but misses the GPU acceleration because they're still attaching JavaScript scroll listeners to drive custom properties that CSS could animate natively. The pre-render-what-you-can, keep-DOM-stable, animate-properties-the-browser-likes advice from the Shopify team only works if you know which properties the browser actually likes, and the four-way terminology collision makes that nearly impossible to learn from search results or AI-generated code snippets.

· · ·

CSS is finally reclaiming control over visual interactions, taking charge of styling, animation, and accessibility exactly as it should. But the Chrome 146 milestone arrives at a moment when the ecosystem can't agree on a shared vocabulary, and that vocabulary gap threatens adoption precisely when the architectural shift offers the clearest wins. Scroll-driven animations have broad support. Scroll-triggered animations just shipped in Chrome 146. Container query scroll states are in draft. View transitions are shipping for same-document and cross-document use cases. Four powerful, distinct features with overlapping names and conflicting mental models.

The path forward isn't more tutorials. It's disambiguation. The specs need clearer naming distinctions. The documentation needs side-by-side comparisons with explicit "use this when" guidance. The migration patterns need to surface which JavaScript patterns map to which CSS properties—Intersection Observer thresholds become `timeline-trigger`, scroll progress calculations become `animation-timeline`, state-dependent styling becomes `scroll-state`, element morphs become view transitions. And the ecosystem needs to stop saying "scroll animations" as a catch-all and start using the specific terms for the specific behaviors, because the architectural shift from JavaScript to GPU-accelerated CSS only delivers on its promise if developers can confidently choose the right tool.

You needed GSAP. Today, you need `timeline-trigger: view()`. That's progress. But if the ecosystem can't stabilize the vocabulary, the Chrome 146 milestone becomes just another feature that works in demos and confuses in production, and the reclamation of interaction logic from JavaScript stalls at the moment of its first concrete browser win.

Sources

Researched from 10 sources. Primary source: css-tricks.com

How this is made

Each issue is researched and written by sift.'s AI editorial pipeline. It reads a curated set of public sources for the magazine (publications, research blogs, newsletters and expert accounts) plus targeted web searches, groups what changed during the week into a few themes, and writes one piece per theme. A separate AI pass checks each piece against its sources before publication. No human editor reviews each article, so check the linked sources before relying on a specific claim.

One issue a week. Free.

sift. reads hundreds of sources so you don't have to, and sends one issue you can actually finish. No daily noise. Unsubscribe anytime.

Also in this issue