
CSS @container scroll-state queries eliminate JavaScript scroll listeners
The key insight: After years of wrestling with JavaScript scroll listeners that fire hundreds of times per second, CSS @container scroll-state queries let you detect scroll behavior—sticky positioning, snap alignment, scroll direction—directly in CSS with zero JavaScript overhead.
This isn't just another CSS feature. It's a fundamental shift toward browser-managed scroll state that eliminates one of the most common performance bottlenecks in modern web development.
The JavaScript scroll listener problem
Every developer has written (or inherited) code like this:
1let lastScrollY = window.scrollY;
2
3window.addEventListener('scroll', () => {
4 const currentScrollY = window.scrollY;
5 const header = document.querySelector('.header');
6
7 if (currentScrollY > lastScrollY) {
8 header.style.transform = 'translateY(-100%)';
9 } else {
10 header.style.transform = 'translateY(0)';
11 }
12
13 lastScrollY = currentScrollY;
14});This approach has fundamental issues: continuous CPU consumption, complex state management, and the constant risk of janky animations when scroll events overwhelm the main thread.
CSS @container scroll-state queries solve this by moving scroll detection into the browser's rendering engine. No event listeners, no manual state tracking, no performance overhead.
How scroll-state queries actually work
The system requires two parts: a container declaration and state-based styling rules.
1/* Declare scroll state container */
2.scroll-container {
3 container-type: scroll-state;
4}
5
6/* Style based on scroll state */
7@container scroll-state(scrolled: bottom) {
8 .hide-on-scroll {Four scroll states are available:
- `stuck`: When sticky elements adhere to viewport edges (top, bottom, left, right)
- `scrolled`: Direction of the most recent scroll action (top, bottom, left, right, none)
- `scrollable`: Whether overflow content exists that can be scrolled to
- `snapped`: When elements align with CSS scroll snap points
<> The browser manages all state detection internally, eliminating the JavaScript performance tax while providing more accurate scroll behavior detection than manual event handling./>
Real-world applications that become trivial
Sticky header styling without JavaScript complexity:
1header {
2 position: sticky;
3 top: 0;
4 background: white;
5 transition: box-shadow 0.2s;
6}
7
8.page {Carousel indicators that highlight snapped items:
1.carousel {
2 container-type: scroll-state;
3 scroll-snap-type: x mandatory;
4 overflow-x: auto;
5}
6
7.carousel-item {
8 scroll-snap-align: center;Progressive scroll indicators that appear only when needed:
1.scroll-hint {
2 display: none;
3}
4
5@container scroll-state(scrollable: bottom) {
6 .scroll-hint {
7 display: block;
8 }
9}Each of these patterns previously required custom JavaScript with scroll event throttling, intersection observers, or complex state management. Now they're declarative CSS with automatic performance optimization.
The performance implications
JavaScript scroll listeners create performance bottlenecks because they:
- Fire continuously during scroll (potentially 60+ times per second)
- Force style recalculations on every event
- Block the main thread with DOM queries and calculations
- Require manual throttling or debouncing to remain performant
CSS scroll-state queries eliminate these issues by moving detection into the browser's optimized rendering pipeline. The browser determines scroll states during its normal layout and paint cycles, avoiding the JavaScript-to-DOM performance bridge entirely.
Browser support and progressive enhancement
Scroll-state queries landed in Chrome 133, with other browsers following the specification. For production use, implement progressive enhancement:
1/* Fallback styles */
2.enhanced-header {
3 background: white;
4}
5
6/* Enhanced experience */
7@supports (container-type: scroll-state) {
8 .enhanced-header {This approach ensures base functionality works everywhere while providing enhanced experiences in supporting browsers.
Why this matters
For individual developers: You can eliminate scroll-based JavaScript from many common UI patterns, reducing bundle size and improving performance.
For teams: Scroll behavior becomes part of CSS architecture rather than scattered JavaScript event handlers, improving maintainability and reducing bugs.
For the web platform: This represents a broader trend toward moving common JavaScript patterns into native browser APIs, following the path of CSS animations, transitions, and custom properties.
Start experimenting with scroll-state queries in Chrome-first environments or development builds. Even if you can't use them in production today, understanding the declarative approach will prepare you for the post-JavaScript-listener future that's rapidly approaching.
The era of scroll event handlers consuming CPU cycles for basic UI interactions is ending. CSS @container scroll-state queries represent the beginning of truly browser-optimized scroll interactions.
