v145 · html · overlays

Light dismiss, on click rather than on press

"Click outside to close" sounds unambiguous until you decide which event means click. Dismissing on pointerdown closes a menu when someone starts a scroll gesture over the page behind it, and when they right-click. Moving the decision to the click event fixes both, and makes the rule the same one users already have in their heads.

concepts

  1. What closedby resolves to

    Three keywords, two missing-value defaults depending on whether a dialog is modal, and an invalid value that quietly becomes something. All of it measured on real dialogs in three states.

  2. Which event closed it

    Open a popover, dismiss it, and read back the exact sequence of pointer and mouse events with the moment it closed marked in the middle. Includes a dispatched-event control that proves what does not dismiss.

  3. A menu over a scrollable list

    The two bugs, as a checklist you complete by doing them: scroll the page under an open menu, and right-click behind it. The page watches and records whether the menu survived — you do not have to report it yourself.

why it shipped

Light dismiss used to trigger on the pointer going down. That is the earliest possible moment, which felt responsive and was wrong in two common cases. A touch drag that begins outside a popover starts with a pointerdown — so a menu closed the instant a user began scrolling the page behind it, before they had moved a pixel. And a right-click anywhere outside dismissed the overlay before the context menu appeared over the top of it.

The mechanism now hangs off the click event instead. A click requires a press and a release on the same target, so a drag is not a click, and a secondary button press is not one either. Both bugs disappear, and nothing else about the behaviour changes.

the API

<!-- A popover with the default auto behaviour: light dismiss and Escape. -->
<div popover>…</div>

<!-- A dialog opts in explicitly, and can ask for one without the other. -->
<dialog closedby="any">…</dialog>          <!-- light dismiss + Escape -->
<dialog closedby="closerequest">…</dialog> <!-- Escape only -->
<dialog closedby="none">…</dialog>         <!-- neither -->

The default differs by how the dialog was opened: a modal dialog behaves as closerequest, a non-modal one as none. That is measurable, and the first demo measures it.

references