CSS scroll-state container queries apply styles according to a container's scrolling condition. They can detect whether more content is available in a direction, whether a sticky element is currently stuck, whether an item is snapped, or which direction was most recently scrolled.
These queries extend the container-query system beyond size and style. Browser support still varies in 2026, so use them as a progressive enhancement and keep the interface understandable without the conditional styles.
Create a Scroll-State Query Container
Set container-type: scroll-state on the element whose state you want to query. An optional container name makes the target explicit.
Example:
.message-list {
/* Allow descendants to query this element's scroll state. */
container-type: scroll-state;
container-name: messages;
max-height: 18rem;
overflow-y: auto;
}
The @container rule styles descendants of the query container when its condition matches.
Syntax:
@container messages scroll-state(scrollable: bottom) {
/* Styles applied while more content can be reached below. */
.more-content-hint {
opacity: 1;
}
}
Scroll-State Query Types
| Descriptor | Question it answers | Typical use |
|---|---|---|
| scrollable | Can the container scroll in a direction? | Show more-content indicators |
| scrolled | Which direction was most recently scrolled? | Reveal or hide navigation |
| snapped | Is the element snapped on an axis? | Highlight the active snap item |
| stuck | Is a sticky element attached to an edge? | Change a sticky header appearance |
Detect Remaining Scrollable Content
The scrollable descriptor can test the top, right, bottom, left, block, inline, x, or y direction. The condition matches when user scrolling can move the content in that direction.
Example:
.notifications {
container-type: scroll-state;
container-name: notifications;
overflow-y: auto;
max-height: 15rem;
}
@container notifications scroll-state(scrollable: bottom) {
/* Display a fade while unseen notifications remain below. */
.bottom-fade {
opacity: 1;
}
}
@container notifications scroll-state(scrollable: top) {
/* Reveal a back-to-top control after scrolling down. */
.back-to-top {
visibility: visible;
}
}
A container may be scrollable in both directions when the current position lies between the start and end.
Style a Sticky Element When It Is Stuck
Set the sticky element itself as a scroll-state query container. Its descendants can react when the element reaches a sticky boundary.
Example:
.section-header {
position: sticky;
top: 0;
container-type: scroll-state;
container-name: section-heading;
}
@container section-heading scroll-state(stuck: top) {
/* This child changes only while the header is stuck. */
.heading-content {
background: #1f3a63;
color: white;
box-shadow: 0 2px 8px #0004;
}
}
The rules inside a container query can style descendants, not the query container itself. Wrap the visible header content in a child when it needs to change.
Highlight a Snapped Item
Scroll snapping creates predictable stopping points. A snapped query can emphasize the item chosen by the snap container.
Example:
.slide {
scroll-snap-align: center;
container-type: scroll-state;
container-name: slide;
}
@container slide scroll-state(snapped: inline) {
/* Highlight content inside the active snapped slide. */
.slide-content {
scale: 1;
opacity: 1;
}
}
Use the axis that matches the scroll-snap layout. An inline query suits a horizontal carousel in the usual writing mode.
Respond to the Recent Scroll Direction
The scrolled descriptor identifies the direction of the most recent scroll. This can support an interface that reveals navigation when the visitor scrolls toward the start.
Example:
html {
container-type: scroll-state;
container-name: page-scroll;
}
@container page-scroll scroll-state(scrolled: top) {
/* Bring the navigation back when scrolling upward. */
.site-header {
translate: 0 0;
}
}
@container page-scroll scroll-state(scrolled: bottom) {
.site-header {
translate: 0 -100%;
}
}
Support note: Individual scroll-state descriptors can reach browsers at different times. Test each condition you use instead of assuming every descriptor has identical support.
Complete Runnable Example
This example displays a sticky hint while additional list items remain below the current scroll position.
Example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CSS Scroll-State Query Example</title>
<style>
body { font-family: Arial, sans-serif; }
.course-list {
width: 320px;
height: 180px;
overflow-y: auto;
border: 1px solid #888;
container-type: scroll-state;
container-name: courses;
}
.course-list ul { margin: 0; padding: 1rem 2.5rem; }
.scroll-hint {
position: sticky;
bottom: 0;
margin: 0;
padding: .5rem;
color: white;
background: #2457a6;
text-align: center;
opacity: 0;
}
/* Show the hint while more content exists below. */
@container courses scroll-state(scrollable: bottom) {
.scroll-hint { opacity: 1; }
}
</style>
</head>
<body>
<h1>Available Courses</h1>
<div class="course-list">
<ul>
<li>HTML</li><li>CSS</li><li>JavaScript</li>
<li>Python</li><li>Docker</li><li>Laravel</li>
<li>SQL</li><li>Software Testing</li>
</ul>
<p class="scroll-hint">Scroll for more courses</p>
</div>
</body>
</html>
Provide a Usable Fallback
Write the default interface first, then place the enhancement inside the container query. Unsupported browsers ignore the conditional block.
Example:
/* The scrollbar remains the default indication. */
.more-content-hint {
display: none;
}
/* Supporting browsers add an extra hint while content remains below. */
@container messages scroll-state(scrollable: bottom) {
.more-content-hint {
display: block;
}
}
Unsupported browsers keep the ordinary scrolling interface and its scrollbar. If the hint is essential, use JavaScript or a permanently visible control instead of depending entirely on scroll-state queries.
Best Practices
- Name query containers in layouts that contain several scrollable regions.
- Match logical directions such as block and inline to writing-mode-aware designs.
- Keep essential navigation and instructions usable when queries are unsupported.
- Avoid large visual jumps when a scroll condition changes.
- Test keyboard scrolling, touch input, scrollbars, reduced-motion preferences, and nested scrollers.
Conclusion
CSS scroll-state queries let descendants respond directly to scrollability, sticky positioning, snap state, and recent scroll direction. They can replace some scroll-event scripts with declarative styles, but current support calls for progressive enhancement. Start with a complete fallback, then use focused queries to improve feedback and navigation.