Animation Intermediate

Sticky & snapped element styling without JavaScript

Styling elements differently when they become stuck (sticky) or snapped used to require JavaScript scroll event listeners. scroll-state() container queries let CSS respond to scroll-related states directly.

Modern
8 lines
1.header-wrap {
2  container-type: scroll-state;
3}
4
5@container scroll-state(
6  stuck: top
7) {
8  .header { box-shadow: 0 2px 8px #0002; }
9}
Old 14 lines
1/* JS scroll listener approach */
2const header = document
3  .querySelector('.header');
4const offset = header
5  .offsetTop;
6
7window.addEventListener(
8  'scroll', () => {
9    header.classList
10      .toggle('stuck',
11      window.scrollY
12        >= offset);
13  }
14);
Limited availability 50% global usage

This feature is not Baseline because it does not work in some of the most widely-used browsers.

135+
135+
scroll inside to see the header change
Sticky Header stuck
Content row 1
Content row 2
Content row 3
Content row 4
Content row 5
Content row 6
Content row 7
Content row 8
Content row 9
Content row 10
Content row 11
Content row 12
Content row 13
Content row 14
@container scroll-state(stuck: top)
Kinsta

Your first month is free

Managed WordPress hosting for faster sites.

Learn more
⚑

No scroll listeners

The browser tracks stuck and snapped states internally. No scroll event handlers, no requestAnimationFrame, no jank.

✦

Multiple states

Query stuck (top, bottom, left, right), snapped (x, y, inline, block), and overflowing states β€” all with pure CSS.

∞

Container query model

Uses the familiar @container syntax. If you know container queries, you already know how to use scroll-state queries.

States
stuck, snapped
Plus overflowing
Old Approach
JS scroll events
addEventListener
Modern Approach
scroll-state()
Container query

How it works

Sticky headers that add a shadow when stuck, or carousels that highlight the active slide β€” these patterns always required JavaScript. You'd listen to scroll events, calculate positions, and toggle classes. This causes layout thrashing, jank, and race conditions.

scroll-state() container queries let CSS detect scroll-related states natively. Set container-type: scroll-state on the scrolling ancestor, then query it: @container scroll-state(stuck: top) matches when a child is stuck to the top. @container scroll-state(snapped: x) matches the currently snapped element. The browser handles all the tracking with zero JavaScript.

New CSS drops.

Join 600+ readers who've survived clearfix hacks.

ESC