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.
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.
| Condition | CSS query | Indicates |
|---|---|---|
| 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 ↗