html icon indicating copy to clipboard operation
html copied to clipboard

Consider preventing page scroll when modal dialog is visible

Open scottaohara opened this issue 3 years ago • 37 comments
trafficstars

Another UX/a11y improvement for the dialog element would be to ensure that, when in the modal state, the underlying document cannot be scrolled. This will help ensure that the invoking element for the dialog will remain in the visible viewport, and when focus is returned to the document, the viewport will not have to re-scroll to ensure this element is in view. This topic was originally surfaced by Curtis Wilcox on the web a11y slack channel.

I have created a quick demo to show how the underlying document is presently scrollable while the modal dialog is visible: https://codepen.io/scottohara/full/YzYGpNy

One caveat I can think of with this idea is that if an author makes a dialog that is too large for the current viewport, then the underlying document will need to be able to scroll so as to allow someone to view all the content of the dialog.

Though, one could also argue that this could be, at least partially, mitigated by setting a max-height/width to the dialog so that it does not extend beyond the bounds of the browser viewport. Authors will need to do something like this to some degree to meet wcag success criterion 1.4.10: Reflow.

scottaohara avatar Mar 21 '22 23:03 scottaohara

Thank you Scott for raising the issue here.

If scrolling the underlying document is to be prevented, I think to be thorough it would also need to be prevented in any child scrollable element not in the modal dialog.

Scott's demo includes an option, Enable overscroll-behavior: contain, an example of how the issue could be mitigated. I've found in Firefox (v98.0.1 for Mac) the property doesn't prevent scrolling at all and it doesn't always prevent scrolling in Chrome (v99.0.4844.83 for Mac). As mentioned in the demo, it doesn't prevent keyboard keys from scrolling the document; since it also doesn't prevent clicking and dragging the document scrollbar, this is more equitable behavior (the dialog ::backdrop prevents use of scrollbars within the document).

A well-designed modal dialog should include whatever information is needed to complete the intended tasks within. Nevertheless, there could be situations where it's beneficial to users who can see the underlying document to be able to still scroll it: A product in a dialog shopping cart can be compared to products listed in the underlying page; while filling out a form in a dialog, someone with difficulty recalling information from the page may be able to scroll it into view. All of this is dependent on how visible the document is through the ::backdrop and what the modal dialog is covering.

extra808 avatar Mar 22 '22 15:03 extra808

Is blocking scrolling something we should special case for <dialog> ? or should we do this to all nodes that are inert?

I think forcing overflow: hidden may be a bit disruptive for websites where some content is expected to be visible just with overflow: visible, especially if done for all inert nodes. We may be able to force overscroll-behavior: contain though I don't know how this solves this particular issue.

Special casing <dialog> isn't particularly great IMO.

nt1m avatar Mar 24 '22 16:03 nt1m

@nt1m good point about not wanting to special case, completely agree.

There definitely could be benefit to doing this for other inert nodes, especially if they represent other types of 'popups' where focus navigation should be trapped to the popup so long as it is invoked - and thus accidentally scrolling the underlying document in those cases could either result in an unwanted dismissal of the popup, or a visual separation of the popup from its invoking element.

But, maybe this is more an explicit opt in for authors? Whether that be an html feature to do so, or author guidance on how to do this to in a standardized way. I can think of both situations where I'd absolutely want this, and others where it'd be less than ideal.

scottaohara avatar Mar 25 '22 01:03 scottaohara

Just hit this issue and wondered what the current consensus is? Arrived here having done what I consider the 'common sense' thing and applied overscroll-behavior: contain to the dialog to ensure the trigger for what caused the dialog in the first place remains in situ.

Is the current situation that there is no 'proper' way to prevent overscroll with dialog elements?

benfrain avatar Oct 17 '22 14:10 benfrain

This has also come up for me today. I followed Adam Argyles excellent article : https://web.dev/building-a-dialog-component/

Although it;s come back that they expected the whole page to scroll rather than content inside the modal/dialog. I guess the default behaviour is inert which blocks focus and click events. It could be argued you are no longer interacting with whats behind the modal so it shouldn't scroll.

Also fromthe w3c

. Windows under a modal dialog are inert. That is, users cannot interact with content outside an active dialog window. Inert content outside an active dialog is typically visually obscured or dimmed so it is difficult to discern, and in some implementations, attempts to interact with the inert content cause the dialog to close. A

Adam just responded on twitter

Scrolling shouldn't happen behind the modal, things behind the modal should be considered inert / hidden

trickydisco78 avatar Nov 03 '22 13:11 trickydisco78

CSS provides a way to do this:

dialog {
  overflow: auto;
  overscroll-behavior: contain;
}

This should work in browsers, but it doesn’t because they all have the same bug: https://github.com/w3c/csswg-drafts/issues/3349#issuecomment-492721871.

I think the best path forwards is for browsers to fix this bug.

simevidas avatar Jun 06 '23 20:06 simevidas

This should work in browsers, but it doesn’t because they all have the same bug: w3c/csswg-drafts#3349 (comment).

But will it work also, when the pointer is over the backdrop area and the mouse wheel is used? Because currently, even when the dialog is scrollable and the overscroll-behavior blocks the scrolling outside when my cursor is over the dialog area, it doesn't block when the cursor is over the backdrop area.

Also for scrolling with keyboard it doesn't seem to work at all.

pepkin88 avatar Jun 27 '23 12:06 pepkin88

@pepkin88 When I do this:

dialog, ::backdrop {
  overscroll-behavior: contain;
}

attempting to scroll while the cursor is over the backdrop:

  • Chrome: scrolls the dialog
  • Safari and Firefox: scrolls the page

Test page: https://output.jsbin.com/puwojoy/quiet

I assume Chrome’s implementation is acceptable. In that case, we should investigate why the other browsers behave differently and maybe try to get them to align with Chrome.


Regarding scrolling with the Arrow Up and Down keys while the dialog is open (same test page):

  • Chrome: page starts scrolling after dialog scrolls to end (overscroll-behavior: contain is ignored)
  • Safari and Firefox: page does not start scrolling in this case

This seems to be a five-year-old Chromium bug:

https://bugs.chromium.org/p/chromium/issues/detail?id=824555

simevidas avatar Jun 27 '23 12:06 simevidas

Interesting. I checked this test page. On Chromium, it seems that when my cursor is over the backdrop pointing on the hr element, the outer scroll is blocked. But when the cursor is not over hr, e.g. is over the p element, the page is scrolling. Weird.

pepkin88 avatar Jun 27 '23 13:06 pepkin88

I can confirm. The same happens when hovering the ”tab stop” inputs. This could be another Chromium bug.

I updated the test page from <hr size=100> to <hr style="margin-block: 50px"> to make it easier to reproduce page scrolling in Chrome.

simevidas avatar Jun 27 '23 13:06 simevidas

@simevidas can confirm using your https://output.jsbin.com/puwojoy/quiet test page the following when on Firefox (Linux):

  • When cursor is on the page, scroll wheel scrolls the page
  • When cursor is on modal, scroll wheel scrolls the dialog

markcellus avatar Jun 27 '23 16:06 markcellus

This is a comment from the Accessible Platform Architectures (APA) Working Group.

The APA WG supports this proposal; it would improve accessibility for people who are experiencing a range of vision and cognition barriers.

In our discussion, one of our members (@AutoSponge) suggested that animations that are happening on the page behind the dialog should be paused; we feel that would help accessibility too. (That would be a separate discussion, but I mention it here to gauge interest; we'd be happy to file a new issue.)

matatk avatar Jul 05 '23 13:07 matatk

This is a very common issue in the webdev with dozens of hacks and no good solution. I was hoping that once we will be able to use native dialog it would finally go away, but it appears it is still present. I realize that it's mostly not dialog's fault, but rather two bugs [1] [2] that are there forever. But it still would be nice if dialog had it solved. After all "modal" seems to impose that dialog is the only piece of the page I'm able to interact with, at least that's what I thought.

waterplea avatar Sep 22 '23 14:09 waterplea

Want to add a +1 to this idea. Every time I've ever implemented modal dialogs I've had to add JavaScript to add overflow: hidden to the html element when open and remove it when closed. It'd be nice if this was automatically handled by dialog (or inert more generally)

lukewarlow avatar Oct 06 '23 23:10 lukewarlow

I tried to build with <dialog> for the first time today (in Chrome), then I hit this, and found this issue.

There is no robust reliable way prevent the issue (at least in Chrome). f.e.

  • overscroll-behavior: contain does nothing if the dialog is not a scroll container, and it is even more odd when the dialog is full screen and content below still scrolls
  • iframes ignore overscroll-behavior,
  • preventingDefault on wheel and other events works in some cases but not all, etc, and getting these handlers into iframes may even be impossible
  • inert attribute on document.body is ignored (EDIT: Oh, that doesn't prevent scroll regardless)
  • etc

The only way to do it is non-robustly: f.e. set overflow: hidden on the scrolling ancestor, etc, but this can break other people's styling (f.e. the modal dialog is made by a library component and should not touch other DOM).

The out-of-the-box behavior (at least in Chrome) is not a great UX.

Is there a Chromium issue tracking this specifically? I didn't see one, but maybe I didn't search well enough.

trusktr avatar Dec 30 '23 05:12 trusktr

I’m linking the Chromium bug re overscroll-behavior: contain not working for dialogs without overflow:

https://bugs.chromium.org/p/chromium/issues/detail?id=813094

simevidas avatar Dec 30 '23 12:12 simevidas

u can fix this scrolling with the following CSS in the global CSS file: body:has(dialog[open]) { overflow: hidden; }

ciprianmacovei avatar Jan 09 '24 13:01 ciprianmacovei

u can fix this scrolling with the following CSS in the global CSS file: body:has(dialog[open]) { overflow: hidden; }

Yes, it's a partial fix, but it's not perfect, because it hides the scrollbar, which may cause layout shifts.

pepkin88 avatar Jan 09 '24 16:01 pepkin88

Yes, it's a partial fix, but it's not perfect, because it hides the scrollbar, which may cause layout shifts.

I can confirm layout shifts when the scrollbar is hidden, which I was hoping to get rid of using the native dialog component. I'm honestly surprised theres no way yet to control the scroll behavior.

katerlouis avatar Mar 10 '24 12:03 katerlouis

You can use scrollbar-gutter: stable to prevent the layout shift.

lukewarlow avatar Mar 10 '24 13:03 lukewarlow

scrollbar-gutter: stable is a decent solution, but the downside is that I can't control what is rendered on the gutter. I tried covering it somehow, but it doesn't work.

pepkin88 avatar Mar 11 '24 09:03 pepkin88

To fix that issue you can use this trick for now:

.main-wrapper {
  padding-left: calc(100vw - 100%);
}

It applies the same padding on the left, as the scrollbar does on the right when present.

Unfortunately most of the time you can not apply this directly to body, as full width children like navbars would no longer work out of the box. Hence the .main-wrapper.

webbertakken avatar Mar 14 '24 00:03 webbertakken

u can fix this scrolling with the following CSS in the global CSS file: body:has(dialog[open]) { overflow: hidden; scrollbar-gutter: stable; }

Something that came up from a discussion on this recently is this won't work if the dialog is inside a shadow tree as the has won't pierce the boundary.

lukewarlow avatar Mar 29 '24 18:03 lukewarlow

I will add, just because it is rarely mentioned, that this also may cause a jump to the vertical beginning of the document. If a model is opened further down the document and overflow: hidden; is set to the body or <html>, readers may be yoinked to the beginning of the document.

A reliable way to prevent scrolling in place in case that a ::backdrop or top layer is visible would be much appreciated.

nachtfunke avatar Apr 18 '24 14:04 nachtfunke

I'm using this function I found in melt-ui, and it works great in my case https://github.com/melt-ui/melt-ui/blob/f15cd300735c1abe95286d32956dfe3b403f9f0d/src/lib/internal/helpers/scroll.ts#L39

xob0t avatar May 01 '24 22:05 xob0t

Bit of a hack, but thought I'd share what. I ended up doing.

body:has(dialog[open]) {
    overflow: hidden;
}

BritishWerewolf avatar May 05 '24 22:05 BritishWerewolf

The overflow:hidden hack has been mentioned quite a few times in this thread already :) It's a good workaround for now, but it's hacky and can cause some negative effects, which have also been pointed out many times in the thread. Hopefully there's a cleaner solution soon!

markcellus avatar May 05 '24 23:05 markcellus

This worked for me.

html:has(dialog[open]) {
  overflow: hidden;
}

IlungaNtita avatar Jun 04 '24 22:06 IlungaNtita

Clearly this feature is needed.

Although, overflow: hidden won't work due to layout shifts. So, should there be a new CSS property to prevent page scroll with the scroll bar showing? Should an issue be opened there?

Like what are we waiting on for this to be standardised

YummyBacon5 avatar Jun 06 '24 18:06 YummyBacon5

To be fair, the proper fix for this issue is known. Here are the steps we need to address this:

  1. overscroll-behavior: contain on the dialog
  2. Browsers ignoring overscroll-behavior for elements with no overflow (Chrome bug, Firefox bug, Safari bug)
  3. Browsers ignoring overscroll-behavior for keyboard scroll with arrows, space and page up/down buttons (Chrome bug, Firefox ✅, Safari ✅)

EDIT: Call me crazy, but I address this issue in my code by adding a little wrapper around my actual dialog content, making it scrollable with hidden scrollbar and then making my dialog content sticky inside so this scroll is not noticeable for the user. This way I mitigate all the issues above and lock scroll inside the dialog even if there's not enough content to overflow.

waterplea avatar Jun 07 '24 06:06 waterplea