demo · v149

Compound scroll-state queries

@container scroll-state() queries can be nested and combined with not. A sticky header can detect not just "am I stuck?" but also "is there still content below?" — enabling contextual UI that would have previously needed JavaScript to co-ordinate two separate scroll listeners.

Scroll the box below. Watch the header badges: stuck appears when the header is pinned, ↓ more appears while there's more content below (nested compound query), and at bottom flips on when you reach the end.

Section 1: Introduction

Scroll this container down to see the sticky header pin to the top. As it pins, the stuck badge appears — that's a single @container scroll-state(stuck: top) query firing. Scroll further and a "↓ more" hint appears while more content remains below — that's a compound nested query combining stuck state with the outer container's scrollable state.

Section 2: Compound Queries

The "↓ more" hint uses two nested @container rules: the inner one checks stuck: top on the header, and the outer one checks scrollable: down on the scroll container. Both conditions must be true simultaneously for the hint to appear.

Section 3: Contextual UI

When you reach the bottom, the "↓ more" hint disappears (because the container is no longer scrollable downward) and the green "at bottom" badge appears. This uses a not scroll-state(scrollable: down) condition nested inside a scroll-state(stuck: top) query — three logical conditions expressed entirely in CSS.

Section 4: No JavaScript Required

None of these state changes require any JavaScript event listeners, IntersectionObserver, or scrollTop polling. The browser computes sticky-stuck and scrollable states internally and exposes them through the container query mechanism — the same mechanism that drives responsive design, just applied to scroll state.

Section 5: End of Content

You've reached the end. Notice the "at bottom" badge in the header — the compound not scroll-state(scrollable: down) query has fired because there's no more content to scroll to. This is a completion indicator with zero JavaScript, driven entirely by the scroll container reporting its own state.

ConditionCSS queryIndicates
Header pinned @container header-state scroll-state(stuck: top) User has scrolled past the header's natural position
Pinned + content below Nested: stuck-top + scroll-state(scrollable: down) User is mid-content; show "more below" hint
Pinned + at bottom Nested: stuck-top + not scroll-state(scrollable: down) User reached the end; show completion badge
/* Compound: stuck header + content still below */
@container header-state scroll-state(stuck: top) {
  @container outer-scroll scroll-state(scrollable: down) {
    .scroll-down-hint { display: flex; }
  }
}

/* Compound: stuck header + at bottom (NOT scrollable down) */
@container header-state scroll-state(stuck: top) {
  @container outer-scroll not scroll-state(scrollable: down) {
    .at-bottom-badge { display: block; }
  }
}

/* HTML setup:
   outer container = container-type: scroll-state; (for scrollable: down)
   sticky header   = container-type: scroll-state; (for stuck: top) */

see also

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗