Change theme

Scroll-state container queries for sticky interfaces

Published on December 3, 2025
Scroll-state container queries for sticky interfaces

Scroll-state container queries are a new CSS feature that lets authors style elements based on a container’s scrolling state (stuck, snapped, or scrollable) without writing JavaScript. They unlock a cleaner, browser-managed way to build sticky interfaces such as ers that gain a shadow when stuck, snapped captions, or conditional back-to-top controls.

Because the feature is part of the CSS Conditional Rules Module Level 5 and is implemented in Chromium, you can start replacing some IntersectionObserver/scroll-event logic with pure CSS in supported browsers. This article walks through the core ideas, examples, browser support, and practical guidance for shipping sticky UI affordances using scroll-state container queries.

What scroll-state container queries are

Scroll-state container queries extend @container queries so you can query a container’s scroll-related state: whether it is scrollable in a certain direction, whether a position:sticky child is currently stuck to an edge, or whether a scroll-snap target is snapped. The syntax exposes these states as query features that cascade and apply styles to descendant elements.

In practice, this means the browser evaluates a container’s scroll state and applies matching @container rules to children. That removes much of the need to observe element positions with JavaScript, letting the UA manage the state and style updates.

The W3C spec documents these in the CSS Conditional Rules Module (Level 5) under “Scroll State Container Features”, and Chrome DevRel published a practical announcement and demos showing how designers can use them today. See the spec and Chrome blog for formal semantics and examples.

The three core scroll-state features

There are three core features you can query: stuck, snapped, and scrollable. “stuck” detects when a position:sticky element is stuck to an edge (for example, stuck: top). “snapped” detects that a scroll-snap target is currently snapped. “scrollable” answers whether a container can be scrolled in a given direction (block/inline/both) or axis.

Common patterns include @container scroll-state(stuck: top) { … } to add a drop shadow to a er that has become stuck, or @container scroll-state(snapped: true) to style a snapped slide’s caption. The capability to test these states directly in CSS reduces layout thrash and avoids wiring scroll handlers for visual tweaks.

Chrome’s announcement and demos show codepens for sticky-shadow, snapped captions, and back-to-top controls. The MDN guide and W3C examples are also useful references when you want more examples or should-check semantics.

How to use container-type: scroll-state and basic syntax

To enable scroll-state queries, mark the element whose state you want to query with container-type: scroll-state. The element that receives container-type is the container being observed; the @container rules must appear on a descendant element , the responding element cannot be the same element that has container-type. This is a key gotcha to avoid.

Example:

.panel { container-type: inline-size scroll-state; / or container-type: scroll-state / }
.panel .er { position: sticky; top: 0; }
@container scroll-state(stuck: top) {
  .panel .er { box-shadow: 0 4px 8px rgba(0,0,0,0.12); }
}

The @container rule above applies only when the er (a sticky child) is stuck to the top of the panel.

Note that snapped queries require a proper scroll-snap setup (scroll-snap-type on the container and scroll-snap-align on children). Stuck queries only match elements with position: sticky. And because the evaluation is browser-managed, you don’t need to debounce or throttle scroll observers to avoid performance pitfalls.

Sticky-interface use cases and demos

Sticky navbars often gain a subtle elevation when they become stuck so the content underneath reads as layered. With scroll-state queries you can implement that purely in CSS using stuck: top to toggle box-shadow or background tint on the er when it becomes sticky.

Long, sectioned lists can highlight the active section er while it is stuck. Similarly, footers that become sticky at the bottom can reveal extra navigation when stuck: bottom. Other patterns include showing a “back to top” control only when the container is scrollable, or changing caption styles when a slide snaps into place.

Chrome’s DevRel article includes multiple CodePen demos (sticky-shadow, snapped captions, back-to-top) that are useful for quick prototyping. The MDN guide and W3C examples are helpful if you want copyable patterns to adapt for your app.

Browser support, progressive enhancement and fallbacks

Google announced CSS scroll-state() on Jan 15, 2025 and shipped the implementation in Chromium (Chrome 133, Chrome for Android 133, and Edge 133 rolled out in early Feb 2025). At initial rollout the feature was available in Chromium-based browsers while Firefox and Safari showed limited or no support. Web-platform trackers marked the feature as “limited availability” during the early rollout , check live compatibility before relying on it in production.

Can I Use lists the feature for recent Chromium releases and shows a global usage snapshot (the feature page had an example figure around ~62.8% availability at one snapshot); verify the current percentage on caniuse.com before shipping. Because of uneven support, the recommended approach is progressive enhancement: wrap your rules with @supports(container-type: scroll-state) or use feature-detection to enable the CSS-only behavior, and provide JS fallbacks for browsers that lack support.

Polyfills are not a drop-in replacement for browser-managed scroll-state because the UA performs evaluation during post-layout snapshot steps to avoid layout cycles. If you must support non‑Chromium browsers, implement a graceful fallback (a JS observer or alternate UI) rather than expecting perfect parity from a polyfill.

Performance, accessibility, and developer gotchas

One of the biggest benefits of scroll-state container queries is performance: the spec and Chrome docs emphasize that evaluation happens as part of post-layout snapshot steps so the browser manages it without triggering layout cycles. That makes it cheaper and more predictable than many JS-based approaches which can cause reflows if misused.

Accessibility considerations are important. Any scroll-triggered motion should respect users’ motion preferences. Chrome examples explicitly recommend wrapping animations with @media (prefers-reduced-motion: no-preference) so users who prefer reduced motion are not exposed to unwanted animations. Also consider keyboard and focus behaviors for sticky elements.

Developer gotchas include: the responding element must be a child of the element with container-type: scroll-state; stuck only matches position: sticky children; snapped requires a scroll-snap setup; update timing caveats exist to avoid layout cycles; and limited cross-browser support means you should feature-detect and provide fallbacks for non-supporting browsers.

Scroll-state container queries change the way we build sticky interfaces by moving scroll-aware styling into the browser’s CSS engine. For teams targeting modern Chromium users (Chrome/Edge 133+), it’s a great replacement for many JS listeners and IntersectionObserver hacks.

For broader support, use @supports(container-type: scroll-state) progressive enhancement, test real-world demos (Chrome DevRel CodePens, MDN guide), and provide JS fallbacks on browsers without support. As the feature matures across engines, expect more patterns to migrate from JavaScript to declarative CSS.