Skip to content

fix(docsite): pin the desktop hero with sticky so overscroll can bounce again - #5542

Draft
imdreamrunner wants to merge 1 commit into
mainfrom
codex/docsite-desktop-overscroll-sticky
Draft

imdreamrunner wants to merge 1 commit into
mainfrom
codex/docsite-desktop-overscroll-sticky

Conversation

@imdreamrunner

Copy link
Copy Markdown
Contributor

Summary

Step 2 of #5392 — closes #5470. Desktop gets its native overscroll bounce back, and overscroll-behavior-y: none is deleted rather than narrowed.

#5415 fixed the mobile half and left the suppression gated to desktop:

@media (min-width: 1024px) {
  html { overscroll-behavior-y: none; }
}

That rule exists because three home-hero layers were position: fixed — heroContent, HeroFloatingCards.stage and HeroThemeReel.backdropGlow. A fixed layer is glued to the viewport and is not part of the document, so when the document rubber-bands past its own bottom edge the layer does not lift with it. At max scroll the fixed glow occupies the whole viewport, so any bottom overscroll paints hero art in the exposed gap.

The fix

position: sticky pins identically but stays in the document: it lifts with the page and cannot paint below the document's own bottom edge. That is structural, not empirical, which is why the rule can go entirely.

Sticky is clamped to its containing block, and the pinned layers live inside the 760px hero band, so on their own they would unpin the moment the band scrolled past. The new HeroPinRail is that containing block: absolute; inset: 0 against heroScope (hero + showcase), so a sticky child stays pinned for exactly as long as the pin-and-cover effect runs and releases where the opaque showcase already covers it. Being absolute it contributes no height — heroSpacer still reserves the band and the document is the same size.

Below 1024px the rail is display: contents: no box at all, so the narrow hero lays out exactly as if it were not in the tree. Nothing about mobile changes.

Two simplifications fall out:

  • the glow now shares the cards' pin layer instead of pinning itself, which drops both of its media-query branches (position and top become unconditional);
  • stage becomes absolute inside that layer instead of fixed.

navBackdrop stays fixed on purpose: it covers the header strip, and the header sits above heroScope in flow, so there is no box inside the hero for a sticky band to stick to. At a header-height tall it never reaches the bottom gap.

Verification

Production builds of main and this branch served side by side (NEXT_PUBLIC_DOCS_TARGET=latest, so no canary banner), driven with Playwright at DPR 1 with transitions disabled.

Pixel-identical to fixed at every scroll position and viewport:

scroll 0 100 200 400 760 1400 2200 max
1280×900 0 0 0 0 0 0 0 0
1024×768 0 0 0 0 0 0 0 0
1440×1400 0 0 0 0 0 0 0 0
1280×600 0 0 0 0 0 0 0 0
768×1024 0 0 0 0 0 0 0 0
390×844 0 0 0 0 0 0 0 0

(differing pixels; repeated in dark mode with the same result. A handful of 4/255 antialiasing pixels turn up nondeterministically, including at 390px where the change applies to nothing.) Every layer's rect matches to the tenth of a pixel — only the position keyword differs. Document height, scrollWidth and CTA hit-testing are unchanged at every sample.

The gap. A bottom rubber-band is emulated by appending an N-px transparent spacer and scrolling N further, which reproduces the frame exactly: the scrolled content lifts, fixed layers stay put.

before after
html overscroll-behavior-y ≥1024px none auto
fixed layers painting into the gap (1280×900) 2 — glow + card stage 0
pixels of the gap strip that changed — 74% at 1280×900, 54% at 1024×768, 43% at 1280×600

Before, the exposed strip shows the aurora glow, a floating card and the theme dots below the footer. After, it is the page background and nothing else.

Nothing else on the site bleeds. The rule was app-global, so I checked every route for fixed chrome that would now show in the gap: /, /docs/getting-started, /components, /components/Button, /templates, /themes, /blog, /changelog, /community, /playground — 0 painters on all ten. The docs sidebar is sticky, not fixed.

Live behaviour matches main — theme swap mid-scroll (layers hold at 33,48 through the swap), resize while scrolled and back, prefers-reduced-motion, auto-advance, hover-to-pause over both the hero column and the gutters, and the CTA hit test.

Gates: pnpm -F @astryxdesign/docsite test (403 passing), typecheck, build, eslint and prettier on every changed file. Docsite-only, so check-scope skips the heavy jobs and no changeset is required.

The trap this leaves behind, and where it is written down

fixed broke if an ancestor gained transform or filter. sticky breaks additionally on an ancestor overflow: hidden|auto|scroll — silently, with no error: the hero just stops pinning and nobody notices until they look at the home page. .astryx-layout-content (AppShell) already carries overflow: clip, which is safe (clip creates no scroll container) but is one property value away from un-pinning the whole landing page. That warning is in HeroPinRail's file header, which is where anyone touching this will be.

Not covered

Chromium only, and no real trackpad. The emulation matches Chromium's model and proves layout equivalence; the feel of the restored bounce, and Safari, want a look on a Mac — the preview deploy is the place to do it.

…unce again

Step 2 of #5392; closes #5470.

#5415 restored pull-to-refresh on mobile and left the suppression rule
gated to desktop widths:

    @media (min-width: 1024px) { html { overscroll-behavior-y: none } }

That rule exists because three home-hero layers were `position: fixed` —
`heroContent`, `HeroFloatingCards.stage` and `HeroThemeReel.backdropGlow`.
A fixed layer is glued to the viewport and is not part of the document, so
when the document rubber-bands past its own bottom edge the layer does not
lift with it. At max scroll the fixed glow occupies the entire viewport, so
any bottom overscroll necessarily paints hero art in the exposed gap.

`position: sticky` pins the same way but stays in the document: it lifts
with the page and cannot paint below the document's own bottom edge. It is
clamped to its containing block, though, and the pinned layers sit inside
the 760px hero band — on their own they would unpin as soon as the band
scrolled past.

`HeroPinRail` is that containing block: `absolute; inset: 0` against
`heroScope` (hero + showcase), so a sticky child stays pinned for exactly as
long as the pin-and-cover effect runs and releases where the opaque showcase
already covers it. Being absolute it contributes no height, so `heroSpacer`
goes on reserving the band and the document is the same size. Below 1024px
the rail is `display: contents` — no box at all — so the narrow hero lays out
exactly as if it were not in the tree.

The glow now shares the cards' pin layer instead of pinning itself, which
drops both of its media-query branches, and `stage` becomes `absolute`
inside that layer. `navBackdrop` stays `fixed`: it covers the header strip,
which sits above `heroScope` in flow, so there is no box inside the hero for
a sticky band to stick to — and at a header-height tall it never reaches the
bottom-overscroll gap.

With nothing fixed left in the hero's art, `overscroll-behavior-y: none` is
deleted outright rather than narrowed, and desktop gets its native
rubber-band back.

Test plan:

Production builds of `main` and this branch served side by side
(`NEXT_PUBLIC_DOCS_TARGET=latest`, so no canary banner), driven with
Playwright at a device scale factor of 1 with transitions disabled.

Pixel-identical to `fixed` at scroll 0 / 100 / 200 / 400 / 760 / 1400 /
2200 / max, at 1280x900, 1024x768, 1440x1400, 1280x600, 768x1024 and
390x844, in light and dark mode — 0 differing pixels in every frame bar a
handful of 4/255 antialiasing pixels that also appear at 390px, where the
change applies to nothing. Document height, `scrollWidth` and CTA
hit-testing unchanged at every sample.

Overscroll gap emulated by appending an N-px transparent spacer and
scrolling N further, which reproduces the rubber-band frame exactly (the
scrolled content lifts, `fixed` layers stay put). At 1280x900 the gap went
from 74% hero art — glow, a floating card and the theme dots — to 0
differing pixels of anything: no `fixed` layer intersects the gap on any of
/, /docs/getting-started, /components, /components/Button, /templates,
/themes, /blog, /changelog, /community or /playground.

`getComputedStyle(html).overscrollBehaviorY` goes `none` -> `auto` at
desktop widths and stays `auto` at 390/768. Theme swap mid-scroll, resize
while scrolled, `prefers-reduced-motion`, auto-advance and hover-to-pause
all match `main` exactly.

Gates: `pnpm -F @astryxdesign/docsite test` (403 passing), `typecheck`,
`build`, eslint and prettier on every changed file. Docsite-only, so CI's
`check-scope` skips the heavy jobs and no changeset is required.

Not covered: Safari, and a real trackpad. The emulation matches Chromium's
model (fixed layers stay, scrolled content lifts) and proves layout
equivalence; the *feel* of the restored bounce wants a look on a Mac.
@vercel

vercel Bot commented Aug 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview Aug 26, 2026 3:28am

Request Review

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Aug 26, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 26, 2026
@astracat-bot

astracat-bot Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 20, 2026, 1:12 AM UTC

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the careful write-up: the sticky rail keeps the hero identical at rest, and the bottom overscroll gap is clean on this branch. Approval waits on a maintainer to provide or identify the governing spec for restoring native overscroll at desktop widths on every Docsite route, since no current spec covers page-level overscroll. Separately, a top bounce on the home page leaves the fixed navBackdrop band at the top of the gap and drops the nav onto the plain page background (on the dark slide its light text sits on white), so the nav tint needs to stay attached to the nav during overscroll. The branch also needs a rebase, because page.tsx conflicts with main.

[Automated review]

Comment on lines +174 to +175
// hero for a sticky band to stick to. It is only a header-height tall, so it
// never reaches the bottom-overscroll gap the pinned art used to bleed into.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True for the bottom gap, but with the overscroll rule gone a top bounce moves the transparent nav down with the document while this fixed band stays at the top: the slide tint is left in the gap and the nav sits on the plain page background (on the Gothic slide its light ink lands on white). Could the tint travel with the header during overscroll, for example from a layer that scrolls with it?

Comment on lines +20 to +22
* `position: sticky` pins the same way but stays in the document, so it lifts
* with the page and cannot paint below the document's own bottom edge — no
* suppression rule needed. The catch is that sticky is clamped to its

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the bottom of the page the pinned glow (1,050px tall, stuck 48px into a 900px viewport) reaches about 198px past the document's bottom edge; what keeps it out of the bounce gap is .astryx-layout-content's overflow: clip. Switching that clip to visible brings the bleed back with no error, so it is worth naming here as load-bearing next to the hidden/auto/scroll warning below.

This branch was successfully deployed

1 active deployment
Preview — 23473566 Deployed Aug 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docsite: contain the desktop-pinned hero layers so overscroll suppression can be removed entirely

1 participant