Keep the mobile header and announcements pinned while the page scrolls - #1181
Open
busbyk wants to merge 5 commits into
Open
Keep the mobile header and announcements pinned while the page scrolls#1181busbyk wants to merge 5 commits into
busbyk wants to merge 5 commits into
Conversation
Below `lg` the header nav was `position: fixed` with no `top`, so it settled at its static position — beneath the announcement banners — and stayed at that viewport offset. Everything above and below then scrolled underneath it, and the nav ended up sitting over the page content rather than above it. `top-0` alone would not fix this. A sticky element only stays put while its containing block is in view, and the nav's containing block is `<header>`, which is barely taller than the nav itself — it would unstick immediately. So the header is now the sticky element and the nav is plain in-flow content. That also removes the `h-[64px]` spacer, which existed only to reserve the space the fixed nav took out of the flow. A sticky header occupies its own space, so the spacer would now double it. Announcement banners sit above the header and scroll away before it pins, which is the intended behaviour. Desktop is unchanged (`lg:static`). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Preview deployment: https://fixxmobile-header-sticky.preview.avy-fx.org |
Pinning the header surfaced four problems, three of them mine. The admin bar is fixed to the top of the viewport, so a header pinned at top: 0 covered it once the page scrolled. Both are z-50 and the header comes later in the DOM, so it won. Raising the admin bar instead would have put it over the hamburger button, trading one broken control for another, so the admin bar now publishes its height as --admin-bar-height and the header pins below it. The menu panel's max height was hardcoded against a 64px offset while its top came from a measurement, so with announcement banners expanded it could run past the bottom of the viewport. The page doesn't scroll while the menu is open, which left the tail unreachable. Both now come from the same measured value, in dvh so mobile browser chrome doesn't reintroduce it. That measurement only ran on open and on resize. Collapsing the banners from the megaphone while the menu was open moved the header and left the panel behind. It now tracks on a frame for as long as the menu is open, which is the only time the value is used. Anchor targets had nothing keeping them clear of the pinned header. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The row was flex justify-between over three children, so when a center had no active announcements the toggle didn't render and the logo got pushed to the right edge. A spacer opposite the hamburger wouldn't have centered it either: the hamburger is 40px and the toggle is 36px, so the logo sat a couple of pixels off center even when the toggle was there. Three grid tracks with equal outer columns center it in both cases. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The banners sit above the header in the page flow, but both controls that expand them stay put while the page scrolls: the toggle in the sticky mobile header and the fixed pill on desktop. Expanding from either one grew the banner off the top of the screen, and scroll anchoring then held the reader's place, so tapping the toggle appeared to do nothing at all. The only way to read the announcement was to scroll back to the top and find it there. Expanding now returns to the top of the page, where the banners are, and honors prefers-reduced-motion. Collapsing still leaves you where you are, and the banners auto-expanding on first view goes through the provider directly, so a fresh page load doesn't scroll. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Expanding an announcement while scrolled took you to the top of the page to read it, because the banners lived above the header in the flow and scrolled away with it. Pinning them alongside the header is the better answer: the announcement is visible wherever you are, and you keep your place on the page. The sticky element is now a wrapper around the banners and the header rather than the header itself, so the two pin as one block. That makes the scroll to the top redundant on mobile, so the toggle there no longer does it. Desktop is unchanged — the header isn't sticky there and the collapsed pill still scrolls you back up, so it keeps that behavior. Pinning is what makes a long announcement dangerous, since it would sit on the screen permanently, so below lg the banners cap at 35dvh and scroll past that. A 2000px announcement renders 282px tall on a 806px viewport, leaving the header and the page reachable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
busbyk
marked this pull request as ready for review
August 11, 2026 22:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Below
lg, the header nav did not stay pinned while scrolling — it drifted over the page content instead. Found while reviewing the native danger map, but it predates that work and affects every page, so it is split out here rather than shipped inside a large feature PR.Fixing the header surfaced several adjacent problems, and reviewing it turned up two more mobile header bugs that were already there. Those are all in here as separate commits, since they are separable and read better on their own.
Related Issues
None. Pre-existing; noticed incidentally.
Key Changes
The header pins below
lg.MobileNavwasposition: fixedwith notop. A fixed element without an offset resolves to its static position — beneath the announcement banners — and then holds that viewport offset forever, so everything scrolled underneath it and the nav sat over the content. Theh-[64px]spacer that reserved the space the fixed nav took out of the flow is gone with it, since a sticky element occupies its own space.The sticky element is a wrapper around the banners and the header, not the header itself. A sticky element only stays put while its containing block is in view, and neither the nav nor the header is much taller than its own box, so either one alone would unstick almost immediately.
Announcements.tsxnow wraps the banners and the header in one sticky block. Abovelgthat wrapper isstaticand everything behaves exactly as it does onmain.Announcements pin with the header. Previously the banners sat above the header in the flow and scrolled away. Expanding one while scrolled down did nothing visible: the banner grew off the top of the screen and scroll anchoring held your place, so the only way to read it was to scroll back to the top. Now it is on screen wherever you are. Because a pinned banner is permanent screen real estate, the banners cap at
35dvhbelowlgand scroll internally past that — a 2000px announcement renders 282px tall on an 806px viewport instead of taking the whole phone.The header no longer covers the admin bar.
AdminBarisfixed top-0 z-50; the header is alsoz-50and later in the DOM, so a header pinned attop: 0won and hid it from logged-in editors.AdminBarnow publishes its height as--admin-bar-heightand the sticky wrapper pins below it. Raising the admin bar's z-index instead would have put it over the hamburger button.The mobile menu panel tracks the header. Its
topandmax-heightnow both derive from the same measured header bottom (indvh), where the max-height was previously hardcoded against a 64px offset and could run the panel past the bottom of the viewport with no way to scroll to it. The measurement re-runs on a frame while the menu is open, so the panel follows the header when the banners animate open or closed underneath it.The logo is centered in the mobile header. The row was
flex justify-betweenover three children, so when a center had no active announcements the toggle did not render and the logo was pushed to the right edge. A spacer opposite the hamburger would not have fixed it either — the hamburger is 40px and the toggle is 36px, so the logo was a couple of pixels off center even when the toggle was present. It is three grid tracks with equal outer columns now.Anchor targets clear the pinned header via
scroll-pt-16 lg:scroll-pt-0. There are no in-page anchors in the app today; this is so the next one does not land underneath the header.Expanding from the desktop pill scrolls back to the banners. Desktop has the same off-screen-expansion problem, and since the header is not sticky there, pinning is not the answer — the fixed pill returns you to the top instead, honoring
prefers-reduced-motion.How to test
nwac.localhost:3000) at a viewport narrower than 1024px.lgand wider, everything should be unchanged — static header, banners scrolling away, and the collapsed pill scrolling you back to the top when expanded.Screenshots / Demo video
Demo of the original sticky-header commit: https://www.loom.com/share/49c80858169d46579c391bf6b582ec06
Note that the Loom predates the later commits — it shows the banners scrolling away before the header pins, which is no longer how it behaves. Worth re-recording before merge.
Migration Explanation
None — presentational only.
Future enhancements / Questions
35dvhcap on pinned announcements is a judgment call, not a researched number. Worth a look on a real device with a genuinely long announcement.dvhplus sticky is where it historically differs.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.