You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Baklava is now making extensive use of top layer elements (popover and dialog) for things like modals, tooltips, and toast notifications. With modern browser support this is working pretty well, but there are still some limitations.
Issue 1: There is no way (with either JS or CSS) to detect that a dialog element is currently in the top layer. You can mostly do this with the :modal pseudo-class, however this breaks down when you introduce exit animations. Between the moment that dialog was closed and the moment that the exit animation finishes, the dialog will no longer be in :modal state, and it won't have the open attribute anymore either. There is no way to tell that the dialog is actually still in the top layer for styling purposes.
As a workaround, you can apply the styling (positioning etc.) just on the base dialog selector. This works, but it means we cannot generally apply default styling of dialog elements without knowing whether it's going to be used as a modal or not. Maybe we can assume aria-model="true" is set on modal dialogs as an indicator that it is intended to be used as a modal? See the reset code here for workarounds/notes:
Issue 2: There is no (easy) way to change the ordering of top layer elements. They are always displayed in stack order of activation. There is no equivalent of z-index. In addition, there is not even a way to programmatically read the elements that are currently in the top layer.
This can be a problem if we want some elements to always be displayed above the topmost layer, for example global toast notifications that should not be obscured by a modal. See WHATWG discussions here and here, and ecosystem discussions for react-toastify, mui.
A possible workaround is to detect when the top layer changes and then re-triggering showPopover() on the global elements so they remain in the topmost layer. However, this runs into another issue where these global elements will not be interactive if there is a modal open (see issue 3 below).
Issue 3: Popovers not inside the topmost modal are not interactive.
The way modal dialogs work, only content that is nested inside the <dialog> will be interactive, everything else is considered inert. This is a problem if we want to open some popover that is not in that dialog, for example toast notifications which should be interactive and are generally rendered from element high in the DOM.
A possible workaround is here using MutationObserver to detect when things are added to/removed from the top layer and using React's createPortal() to portal global popovers to the topmost modal.
There is a proposed interactivity CSS property that could solve this in the future by allowing certain elements to "escape" inertness.
Some other (minor) issues:
There is no easy way to track dialog elements that open. Unlike with closing where we have the close event, there is equivalent "open" event yet. This is coming soon however with the beforetoggle and toggle events. Coming in Chrome v132.
"Light dismiss", like clicking on the backdrop, to close dialog elements is not (yet) supported natively. A <dialog closedby/> attribute is being implemented. There are also some pretty easy workarounds.
There is no foolproof way to prevent a dialog from being closed by users, see here for motivation. When the user requests the modal to close (for example through the Escape key), a cancel event is fired, and this event is cancelable through .preventDefault(). However, at least in Chrome users can force the close to happen by pressing Escape twice. Looks like this will be resolved with closedby="none" in the near future. Incidentally, role="alertdialog" should apparently also disable Escape according to the spec?
Previously, ::backdrop pseudo-elements would not inherit any CSS properties, which meant that custom properties were not available. This is now fixed in all major browsers, see: https://developer.chrome.com/blog/css-backdrop-inheritance
Focus management: according to the latest spec, browsers should (1) focus the first focusable element in a dialog upon opening (or autofocus if present, or the dialog itself is no focusable items), and (2) should re-focus the last focused element upon dialog close. This seems to be widely implemented in browsers now, but wasn't always the case.
Baklava is now making extensive use of top layer elements (popover and dialog) for things like modals, tooltips, and toast notifications. With modern browser support this is working pretty well, but there are still some limitations.
Issue 1: There is no way (with either JS or CSS) to detect that a dialog element is currently in the top layer. You can mostly do this with the
:modal
pseudo-class, however this breaks down when you introduce exit animations. Between the moment that dialog was closed and the moment that the exit animation finishes, the dialog will no longer be in:modal
state, and it won't have theopen
attribute anymore either. There is no way to tell that the dialog is actually still in the top layer for styling purposes.As a workaround, you can apply the styling (positioning etc.) just on the base
dialog
selector. This works, but it means we cannot generally apply default styling ofdialog
elements without knowing whether it's going to be used as a modal or not. Maybe we can assumearia-model="true"
is set on modal dialogs as an indicator that it is intended to be used as a modal? See the reset code here for workarounds/notes:baklava/src/styling/global/reset.scss
Line 75 in 7f23a79
In Chrome's author styling they use a
:-internal-dialog-in-top-layer
pseudo class for this, but this is not yet standardized.Links:
:-internal-dialog-in-top-layer
Issue 2: There is no (easy) way to change the ordering of top layer elements. They are always displayed in stack order of activation. There is no equivalent of
z-index
. In addition, there is not even a way to programmatically read the elements that are currently in the top layer.This can be a problem if we want some elements to always be displayed above the topmost layer, for example global toast notifications that should not be obscured by a modal. See WHATWG discussions here and here, and ecosystem discussions for react-toastify, mui.
A possible workaround is to detect when the top layer changes and then re-triggering
showPopover()
on the global elements so they remain in the topmost layer. However, this runs into another issue where these global elements will not be interactive if there is a modal open (see issue 3 below).Issue 3: Popovers not inside the topmost modal are not interactive.
The way modal dialogs work, only content that is nested inside the
<dialog>
will be interactive, everything else is considered inert. This is a problem if we want to open some popover that is not in that dialog, for example toast notifications which should be interactive and are generally rendered from element high in the DOM.Discussion:
A possible workaround is here using
MutationObserver
to detect when things are added to/removed from the top layer and using React'screatePortal()
to portal global popovers to the topmost modal.There is a proposed
interactivity
CSS property that could solve this in the future by allowing certain elements to "escape" inertness.Some other (minor) issues:
There is no easy way to track dialog elements that open. Unlike with closing where we have the
close
event, there is equivalent "open" event yet. This is coming soon however with thebeforetoggle
andtoggle
events. Coming in Chrome v132."Light dismiss", like clicking on the backdrop, to close dialog elements is not (yet) supported natively. A
<dialog closedby/>
attribute is being implemented. There are also some pretty easy workarounds.There is no foolproof way to prevent a dialog from being closed by users, see here for motivation. When the user requests the modal to close (for example through the Escape key), a
cancel
event is fired, and this event is cancelable through.preventDefault()
. However, at least in Chrome users can force the close to happen by pressing Escape twice. Looks like this will be resolved withclosedby="none"
in the near future. Incidentally,role="alertdialog"
should apparently also disable Escape according to the spec?This weird rendering issue in Chrome when we have a
<dialog open popover>
combined with an element that hascontain: layout
: https://codepen.io/maikelkrause/pen/YPKepzMAnd a few issues that are now widely fixed in browsers:
In some older browser versions (as per an older version of the spec), modals cannot be nested inside popovers. See ticket: Modals cannot be nested in popovers in some browsers #87. Possibly still an issue in Safari?
Previously,
::backdrop
pseudo-elements would not inherit any CSS properties, which meant that custom properties were not available. This is now fixed in all major browsers, see: https://developer.chrome.com/blog/css-backdrop-inheritanceFocus management: according to the latest spec, browsers should (1) focus the first focusable element in a dialog upon opening (or
autofocus
if present, or the dialog itself is no focusable items), and (2) should re-focus the last focused element upon dialog close. This seems to be widely implemented in browsers now, but wasn't always the case.Further reading:
The text was updated successfully, but these errors were encountered: