unscripted/ui

Header

A sticky app bar that hides as you scroll down and slides back when you scroll up — plus a section header that lifts when it docks. Pure CSS, driven by container scroll-state queries.

scrolled querystuck query

A header does two things while you scroll: it can get out of the way, and it can announce when it’s docked. Container scroll-state queries let a sticky element react to how its scroller is moving, with no scroll listeners, observers, or state.

Hide on scroll down, reveal on scroll up

Scroll down and the bar slides up out of view; the instant you scroll back up, it returns. Because it reads scroll direction rather than position, the bar responds to which way you’re going, not how far down the page you are.

  • The scroll container is marked [container-type:scroll-state], which lets its descendants query its scroll state. The bar itself is a plain sticky top-0 element.
  • @container scroll-state(scrolled: block-end) matches while the container was last scrolled down, and applies translate: 0 -100% to slide the bar away; scroll up and the query stops matching, so it returns to 0. In Tailwind that whole conditional is one utility: [@container_scroll-state(scrolled:block-end)]:-translate-y-full.
  • The demo scrolls inside its own bounded box on purpose. A scrolled query reads the nearest scroll-container ancestor, so a self-contained region keeps each example independent. In a real layout you’d mark the element that actually scrolls, often the document root.

Elevate when it docks

A section header that sits flat in the flow, then lifts with a shadow the moment it pins to the top edge. The shadow is the cue that content is now scrolling beneath it.

  • Here container-type: scroll-state goes on the sticky element itself, and @container scroll-state(stuck: top) matches once it’s pinned to the top edge.
  • Scroll-state queries only style a container’s descendants, so the shadow lives on an inner bar inside the <header> rather than on the header node.
  • It’s the “flat until it sticks, then it lifts” affordance you’d normally build with an IntersectionObserver sentinel. Here it’s one utility and zero JavaScript.

Accessibility

The bar moves only visually. It stays in the DOM, in the tab order, and reachable by keyboard and screen reader the whole time. Nothing is inserted or removed on scroll, so focus and announcements stay stable. Mark the <header> up as a banner/navigation landmark as you normally would.

Reduced motion

The global prefers-reduced-motion rule collapses the slide and the shadow fade to an instant change. The bar still hides and reveals, it just doesn’t travel.

Browser support

Minimum stable version per engine, resolved at build time from MDN's browser-compat-data; Baseline status from the official web-features dataset. Everything degrades gracefully — the “when missing” column is the actual behavior, not a broken page.

FeatureChromeEdgeFirefoxSafariWhen missing
Scroll-state query: scrolled (direction)Limited availability144144The header stays permanently visible — an ordinary sticky header. Nothing is hidden and no content is lost.
Scroll-state query: stuckLimited availability133133The docked header simply does not gain its elevation shadow.

† @supports can’t test @container descriptors, so the badge checks container-type: scroll-state (Chrome 133) — but the scrolled query this uses only works from Chrome 144.