Skip to content

Fix x-trap.noscroll layout shift conflict with scrollbar-gutter#4512

Open
dotfrag wants to merge 1 commit intoalpinejs:mainfrom
dotfrag:patch-1
Open

Fix x-trap.noscroll layout shift conflict with scrollbar-gutter#4512
dotfrag wants to merge 1 commit intoalpinejs:mainfrom
dotfrag:patch-1

Conversation

@dotfrag
Copy link

@dotfrag dotfrag commented Jan 27, 2025

If scrollbar-gutter is set to something other than the initial value 'auto' (e.g. 'stable'), do not modify padding-right. See issue #4511.

This is my attempt to fix it, hoping it's heading to the right direction. I'm using it in my app and it's working. Feel free to discard/modify as you like.

EDIT: Does not appear to work reliably in all cases. I'm trying to understand why, otherwise I'll close it. Fixed, checking the failed test now.


Tested and seems to be working:

scrollbar-gutter undefined => overflow: hidden; padding-right: 15px;

scrollbar-gutter unchanged (defaults to 'auto') => overflow: hidden; padding-right: 15px;

scrollbar-gutter: stable => overflow: hidden;

scrollbar-gutter: stable both-edges => overflow: hidden;


Ready for review.

Caveat: scrollbar-gutter, like many other CSS properties supports the global values inherit/revent/etc... which I'm not accounting for. I believe these are really edge-cases. If you want to check for those, then the if check must be explicit (i.e. check for 'stable' and 'stable both-edges') and the return statements reversed.

@dotfrag dotfrag force-pushed the patch-1 branch 3 times, most recently from 55909e8 to 4dc5cbe Compare January 27, 2025 10:50
@ekwoka
Copy link
Contributor

ekwoka commented Jan 28, 2025

Initial and unset shouldn't impact "get computed style" since that will find what the actual applied value is, not what an elements literal setting is.

@dotfrag
Copy link
Author

dotfrag commented Jan 28, 2025

Ah yes that's true!

@bytespider
Copy link

I would really like this to be merged. I'm using scrollbar-gutter: stable which causes a 15px layout-shift, which is quite frustrating

@dotfrag
Copy link
Author

dotfrag commented Sep 2, 2025

I would really like this to be merged. I'm using scrollbar-gutter: stable which causes a 15px layout-shift, which is quite frustrating

As a temporary workaround you can use the patch tool, i.e. pnpm patch.

@bytespider
Copy link

As a temporary workaround you can use the patch tool, i.e. pnpm patch.

Thanks, I didn't know this was possible, I've been forking all this time!

@dangelion
Copy link

dangelion commented Dec 13, 2025

Why this fix was not already merged?

As a temporary workaround I'm doing this:

html { 
  scrollbar-gutter: stable;
  padding-right: 0 !important:
}

@calebporzio
Copy link
Collaborator

PR Review: #4512 — Fix x-trap.noscroll layout shift conflict with scrollbar-gutter

Type: Bug fix
Verdict: Merge

What's happening (plain English)

  1. When you use x-trap.noscroll to open a modal, Alpine calls disableScrolling() which sets overflow: hidden on <html> and compensates for the disappearing scrollbar by adding padding-right equal to the scrollbar width.
  2. This works great normally — the scrollbar vanishes, but the padding keeps the page from shifting.
  3. But if you've set scrollbar-gutter: stable in your CSS, the browser already reserves space for the scrollbar at all times. The scrollbar gutter stays even when overflow: hidden is set. So there's no layout shift to compensate for.
  4. Alpine still adds the padding-right anyway, which creates a new layout shift — the exact opposite of what it was trying to prevent. Users see a ~15px jump when opening a modal.

The fix: before adding padding-right, check getComputedStyle(document.documentElement).scrollbarGutter. If it's anything other than 'auto' (i.e. 'stable' or 'stable both-edges'), skip the padding-right compensation entirely since the browser is already handling it.

Other approaches considered

  1. Check for scrollbarGutter explicitly against 'stable' and 'stable both-edges' — would be more explicit but fragile if the spec adds future values. The current approach (check for not-'auto') is more future-proof.
  2. Measure actual layout shift — could compare clientWidth before/after setting overflow: hidden to determine if compensation is needed. More robust but more complex than necessary for this case.
  3. Let users opt-out via a modifier (e.g. x-trap.noscroll.nopadding) — adds API surface for a problem the browser can tell us about programmatically. Worse approach.

The PR's approach (check computed style, skip padding if gutter is active) is the simplest correct solution.

Changes Made

  • Added a Cypress test for the scrollbar-gutter behavior. Note: the test gracefully skips in browsers that don't support scrollbar-gutter (like Cypress's Electron), but will actively test in Chrome 94+/Firefox 97+.
  • Test pushed to origin/pr-4512 branch (could not push directly to contributor's fork).

Test Results

  • All 24 focus tests pass (including existing noscroll test).
  • CI on the PR: build passing.
  • The new scrollbar-gutter test skips in Electron (no scrollbar-gutter support) but is ready for Chrome/Firefox CI.

Code Review

The fix at packages/focus/src/index.js:210-218 is clean and minimal:

let scrollbarGutter = window.getComputedStyle(document.documentElement).scrollbarGutter

// ... after setting overflow: hidden ...

if (scrollbarGutter && scrollbarGutter !== 'auto') {
    return () => {
        document.documentElement.style.overflow = overflow
    }
}

The scrollbarGutter && guard correctly handles browsers that don't support the property (returns empty string/undefined) — they fall through to the existing padding-right behavior. The cleanup function correctly only restores overflow since paddingRight was never modified.

One minor note: the code style matches Alpine's conventions (no semicolons, let not const). No issues there.

Security

No security concerns identified.

Verdict

Merge. This is a clean, surgical bug fix for a real problem affecting users of scrollbar-gutter: stable (a modern CSS feature that's increasingly adopted). The fix is 8 lines, logically sound, degrades gracefully in older browsers, and has community demand (2 heart reactions, multiple commenters asking for the merge over the past year). The contributor correctly identified both the root cause and the minimal fix.

To merge: you'll want to cherry-pick the contributor's commit and the test commit from origin/pr-4512, or apply the test directly to the PR branch before merging.


Reviewed by Claude

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants