A sticky navbar that morphs when it sticks, no JS
You know the move: a full-width navbar that, once you scroll past it, shrinks
into a floating pill. Until recently that meant a scroll listener, an
IntersectionObserver, or a sprinkle of JS toggling a class. Not anymore.
Manu Arora showed it off
with pure CSS, and the key is a feature I'd missed: scroll-state container
queries.
Scroll this box. The bar starts edge-to-edge, then shrinks into a floating pill the moment it sticks to the top.
Scroll the box above (in a Chromium browser). The bar starts edge-to-edge, then the instant it sticks, it pulls in and rounds off into a pill.
The whole thing
header {
container-type: scroll-state;
position: sticky;
top: 0;
}
@container scroll-state(stuck: top) {
.nav-bar {
max-width: 56rem;
border-radius: 0.75rem;
background: rgb(255 255 255 / 0.92);
}
}Two parts. container-type: scroll-state turns the sticky header into a
container the browser tracks scroll state for. Then @container scroll-state(stuck: top) matches whenever that container is currently stuck to
the top, and you restyle anything inside it. The browser does the detecting; you
just write the stuck-state styles. Add a transition on the inner element and
the morph animates for free.
The catch
It's Chromium-only right now. No Firefox, no Safari, which is a shame for
something this clean. But it degrades nicely: where scroll-state isn't
supported, the query just never matches and your navbar stays in its default
state. So treat the morph as an enhancement, make sure the un-morphed bar is
perfectly usable on its own, and you can ship it today without breaking anyone.
Via @mannupaaji.
Ask your agent to implement this
Read the full writeup at https://seangeng.com/writing/sticky-navbar-that-morphs-on-scroll.md and implement it in my project.
It covers: A sticky navbar that morphs when it sticks, no JS — scroll-state container queries let the browser tell you when a sticky element is stuck, so you can restyle it on scroll with pure CSS. No scroll listeners, no animation library. Chromium-only for now.
Requirements:
- Follow the technique/approach exactly as described in the writeup.
- Adapt names, colors, and styling to my project's existing conventions.
- If it's a component, make it reusable with sensible props and TypeScript types.
- Keep it accessible: semantic HTML, keyboard support, and respect prefers-reduced-motion.
- When done, tell me which files you created or changed and how to use it.Paste into Claude Code, Codex, Cursor, or any agent. view raw .md