CSS @container scroll-state: Override the JS scroll listener now

Scroll containers have always been a weak point in CSS. For years, if you wanted a navigation bar to change its own style after it became sticky, or a photo to react when it entered the viewport, JavaScript was the only real choice.

Most of us have written the same pattern: add a scroll listener, call it getBoundingClientRect() a few times, flip a few classes, and hope the main thread continues. That’s a lot of machinery for something that should be simple. And when it slips, you’ll get jank – the subtle but frustrating moment when the UI lags behind your scroll.

The pattern started to fade. With the new one @container scroll state feature, CSS can respond directly to the position of an element in its scroll container. No voting. No manual measurement. This isn’t just a styling trick – it changes the way we build scroll-based interfaces. By letting the browser handle state detection within its rendering pipeline, we get smoother motion, more stable frame rates, and much less code.

In this guide, we’ll replace heavy scrolling processors with declarative state queries and let CSS do the work.

🚀 Sign up for The Replay newsletter

Replay is a weekly newsletter for developers and engineering leaders.

Delivered once a week, this is your curated guide to the most important conversations around frontend development, emerging AI tools, and the state of modern software.

Why the old approach doesn’t work

Before moving on to a new approach, it’s worth looking at why the old approach is worth stopping. For more than a decade, scroll-driven effects have been relied upon window.addEventListener('scroll', ...) paired with getBoundingClientRect(). On the surface, this looks simple enough. However, in practice, this gave rise to performance problems that proved difficult to overcome.

Scroll listener issue

When a user scrolls, the browser can fire dozens of scroll events every second. Every time your controller runs, the browser must stop its work, execute your JavaScript, and then determine whether the screen needs to be repainted. It all happened within a tight budget framework. At 60 frames per second, you have about 16.7 milliseconds to get everything done. Do more than that, and the result is visible stuttering.

Problem with broken layout

The bigger problem is Layout Thrashing. When you call getBoundingClientRect()You’re asking the browser to calculate the exact size and position of elements at a moment’s notice. To provide an accurate answer, the browser may have to stop the smooth rendering process and perform a forced synchronous layout.

If you then change the style based on those measurements, such as changing the background color or height, you create a situation where the write occurs right after the read. If this happens during a scroll event, the main thread spends more time recalculating the layout than rendering anything. This causes frames to drop, battery life to decrease, and the UI to feel slow and heavy.

How CSS state queries remove important scroll logic

The problem isn’t because the code is wrong – it’s because we’ve solved the styling problem with the wrong tool. JavaScript is fundamentally important, and we have used it to manage the visual state included in the declarative layer. CSS state queries invert that model. They let the browser’s rendering engine handle detection internally. Instead of checking the position of an element over and over again, we declare a condition: when this element is stuck, apply this force. The browser handles timing, clustering, and optimization, leaving the main thread free for actual application logic.

To make these changes requires a slight change in mindset. Rather than thinking in terms of scroll handlers, we think in terms of container states. Traditional container queries react to size. Scroll state queries react to how an element relates to its scroll port.

Let’s see how to define a scroll state container and use its syntax in practice.

This involves two steps: defining a container that holds the state and writing queries for its child elements to respond to that state.

Turns an element into a queryable container

First, you tell the browser that an element’s scrolling behavior must be observable. Whether it’s stuck, broken, or scrollable, these conditions need to be tracked. You activate it by setting container-type property to the new one scroll-state mark.

/* The element whose state we want to track */
.header {
 position: sticky;
 top: 0;
 container-type: scroll-state;
 container-name: sticky-nav; /* Optional: helps target specific containers */
}

Important rule: As with size-based container queries, an element cannot style itself based on its own scroll state. That @container rules must target children (children or pseudo-elements) of the container.

Query syntax

After determining the container, you can use @container according to the rules with scroll-state() function. The syntax uses simple condition-based logic:

@container <optional-name> scroll-state(<condition>) {
 /* Styles applied when the condition is true */
}

Three core states

That scroll-state() The function currently supports three main queries that replace common JavaScript scroll hacks:

1. Traffic jam (stuck)

This query detects when a position: sticky the element has attached to one of its boundaries. You can target positions like top, bottom, left, rightas well as logical values ​​such as inset-block-start, inset-inline-endor even none.

Use case: Add a shadow, tighten the spacing, or make the logo smaller after the header is locked at the top of the viewport.

@container scroll-state(stuck: top) {
  .nav-inner {
    background: white;
    box-shadow: 0 4px 10px rgba(0,0,0,0.1);
  }
}

2. Snap status (snapped)

This query fires when an element is aligned with the scroll-snap container. You can target alignment along block, inline, xor y axis.

Use case: Emphasize the active slide in a carousel by enlarging it, or displaying a caption when the photo is centered.

@container scroll-state(snapped: inline) {
  .card-content {
    opacity: 1;
    transform: scale(1.1);
  }
}

3. Scrollable status (scrollable)

This query determines whether a container has overflow that can be scrolled in a certain direction. You can check the values ​​like top, bottom, left, rightand other logical directions.



Use case: Show a “Scroll to find out more” prompt when additional content is available, and automatically hide it once the user reaches the end.

@container scroll-state(scrollable: bottom) {
  .scroll-indicator {
    display: block;
  }
}

Browser support and progressive enhancement

Starting in early 2025, scroll state query is a new feature available in Chrome 133 and later. Since this is a nice visual upgrade to have, it lends itself well to progressive upgrades.
You have to put your state specific logic inside an @supports blocks to ensure that users on older browsers still have a functional (albeit static) experience:

/* Fallback: Static styles for older browsers */
.nav-inner {
  background: transparent;
}

/* Enhancement: Dynamic state-based styles */
@supports (container-type: scroll-state) {
  @container scroll-state(stuck: top) {
    .nav-inner {
      background: white;
    }
  }
}

Core use cases

Sticky state (header and navigation bar)

This demo shows a header changing its style when it becomes sticky:

  • It expands and then compresses
  • Calm gradients turn into sharp contrasts
  • Descriptive subtitles disappear

Typically, these changes require complex calculations and JavaScript.

What users see:

  • At the top, there’s a large, hero-style open header
  • Once pasted, it turns into a concise and focused navigation bar

No JavaScript or observers required.

See Pen
@container sticky state by Miracle Jude (@JudeIV)
on CodePen.

Snap status (carousel and gallery)

Carousel with credit. This carousel highlights the active slide as it snaps into place. The focused card zooms in, revealing details, and surrounding cards dim automatically. There is no JavaScript index tracking, and no scroll handler is required.

See Pen
@container snap state by Miracle Jude (@JudeIV)
on CodePen.

Edge detection (scroll indicator)

Smart scroll hints that disappear when not needed. This demo includes scroll instructions with gradients and arrows that:

  • Only appears when there is more content to scroll through
  • It automatically disappears in the end
  • Adjusts to content size

This functionality is usually managed with JavaScript and scroll height comparison.

See Pen
@container scrollable status by Miracle Jude (@JudeIV)
on CodePen.

Conclusion

@container scroll state marks a meaningful change in how CSS handles layout and interaction. Rather than connecting observers or measuring position in JavaScript, we let the browser decide when scroll-related conditions are true and apply styles accordingly. Detection happens inside the rendering pipeline, where it belongs.

The result is simpler code and a smoother interface. No need IntersectionObserverthere are no manual bounding box ticks, and no read-write layout cycles to manage. We describe the state we care about, and the browser handles the rest. This results in the UI feeling stable when scrolled, with fewer moving parts and far less room for performance regression.

Is your frontend hogging your users’ CPU?

As web frontends become increasingly complex, resource-intensive features demand more and more from browsers. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all your users in production, give LogRocket a try.

LogRocket Dashboard Free Trial Banner

LogRocket lets you replay user sessions, taking the guesswork out of why a bug occurred by showing exactly what the user experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks.

LogRocket’s Galileo AI monitors sessions for you, instantly identifying and explaining user pain points with automatic monitoring of your entire product experience.

Modernize the way you debug web and mobile apps — start monitoring for free.

Berita Terkini

Berita Terbaru

Daftar Terbaru

News

Berita Terbaru

Flash News

RuangJP

Pemilu

Berita Terkini

Prediksi Bola

Togel Deposit Pulsa

Technology

Otomotif

Berita Terbaru

Daftar Judi Slot Online Terpercaya

Slot yang lagi gacor

Teknologi

Berita terkini

Berita Pemilu

Berita Teknologi

Hiburan

master Slote

Berita Terkini

Pendidikan

Resep

Jasa Backlink

One Piece Terbaru