whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
8.1k stars 2.66k forks source link

Add light dismiss functionality to `<dialog>` #9373

Open mfreed7 opened 1 year ago

mfreed7 commented 1 year ago

One of the nice features of the Popover API is its light dismiss behavior. In several of the demos of Popover that I've seen, developers are doing something like this:

<button popovertarget=foo>Click me</button>
<dialog popover id=foo>I'm a dialog!</dialog>
<style>
dialog[popover]::backdrop {
  background-color: black;
}
</style>

Using <dialog> with a popover attribute is perfectly fine semantically here, since the content represents a dialog. However, this pattern is being used almost entirely because of the features provided by the Popover API which are missing from the <dialog> element itself. Note the usage of ::backdrop to obscure the backdrop entirely. That indicates that this really is meant to be a modal dialog, because the intent is to focus attention only on the dialog and keep the user from "seeing" the rest of the page. However, popovers aren't modal and as such they don't inert the rest of the page. So in the above example, keyboard users are free to tab-navigate to other content they can't see. Mouse users are free to click "through" the opaque background onto unseen elements. Generally, it'd be better if this was a plain old modal <dialog> and not a popover.

To get around this usage pattern, let's bring the missing functionality to <dialog>. https://github.com/whatwg/html/issues/3567 discusses one of those behaviors, namely declarative invocation of <dialog>. In this issue, I'd like to propose a mechanism to add light dismiss to <dialog>s.

Proposal (subject to bikeshedding):

<dialog lightdismiss> I'm a light dismiss dialog </dialog>

With the lightdismiss attribute present, clicking outside the dialog, or hitting ESC (or other close signals) will have the same affect as calling dialog.close().

Note one nuance, which is different from popover: since there's no concept of "nested" dialogs, if more than one dialog is open at a time, only the topmost (most recently opened) dialog will be closed on each light dismiss action. So if three dialogs are open and the user clicks outside all three of them, only the topmost dialog will close. Generally, nested dialogs is an anti-pattern, but even so, this feels the most natural to me anyway.

lukewarlow commented 4 months ago

Just to provide some quick clarifications (not responding to the core of the comment)

Similarly, if we're modeling the behavior of closedby=closerequest after today's modal

, that means it only closes when pressing Esc.

That's not the case it would follow how close watchers (and dialog/popover) work in supporting browsers. E.g. android back gesture or accesibility commands would also close it.

When using closedby=closerequest, I would like my dialogs (and popovers) to also close when using the Back gesture (on Android).

This is already what the html spec says (as much as platform specific behaviours can be specced), for modal dialogs and popovers, and also as mentioned above how closedby would work.

keithamus commented 4 months ago

Thanks for commenting @mayank99 - it's important to get different perspectives captured here!

If you think about how custom JavaScript-based dialogs/popovers are implemented today, authors have almost full control over which events will close the dialog/popover.

I just want to pick out this part because I think it's important. Given all the customisability JavaScript dialogs/popovers have, I only really see two distinct behaviours from a huge volume of dialogs; those that work with or without a click-backdrop-to-dismiss behaviour or those that don't. I don't see many (or any) where there are significant other differences such as close-on-scroll or close by another keypress or some such.

Could you provide some concrete examples of a dialog or popover being closed during focusout or when scrolled away please? I imagine there won't be any for dialog as this only effects modal dialogs and so focus is trapped - it cannot leave the dialog.

mayank99 commented 4 months ago

@keithamus Sure, here are two examples, both really concerned with popover rather than modal <dialog>s (which is why I think this attribute should be added to popover too).

  1. There are plenty of valid use-cases for closing popovers when focus moves out. The most common ones are "composite" widgets that manage their own focus, i.e. custom listboxes, comboboxes, menus (I believe Github's menus do this; try the "create new" menu at the top).
  2. close-on-scroll is less common. I would definitely expect a tooltip to be closed when scrolling away. I've also seen some larger popovers and comboboxes close when scrolling (I believe Github's hover card does this).

Since 1 is so common and is directly tied to WCAG SC 2.4.11, I would expect the platform to help with it.

I was looking at https://github.com/openui/open-ui/issues/1047 and it definitely sounds like it's not a good default because popover is a lower-level API that can be used to build non-modal dialogs that should not close when focus leaves. But I'm thinking that closedby=any is an opt-in thing anyway, so it's probably safe to close on focus out.

Now, I know this thread is specifically about <dialog>, but these things are closely related so I thought it was on topic. It's also directly relevant if you use something like <dialog role="menu"> to build a menu that should close on focusout.