-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
@wordpress/components
: modality of popover-based components
#63674
Comments
I'll share my initial thoughts:
I'd opt for most components to be
We should in most cases, apart from:
I personally like Having said that, that's a topic with more nuances, and I decided to discuss it separately in #63697
I think that this behavior is sensible and I wouldn't change it. I would start without exposing dedicated props in our components, and see if we can get away with it.
I am.
We will need to keep an eye on a few aspects: Interactions between popover-based components that use different underlying componentsFor example, a dropdown menu that opens a modal. We should be able to get around such issues because our new components will expose enough APIs that we could mimic the legacy Ideally, when all popover-based components use Introducing more modality in the editorAs of today, most popover-based components are not |
@wordpress/components
: modality of popover-based components
In general, I'm fine with introducing more modality in most of the popover-based components. That's how native controls work, and when playing with Storybook, I can see quite a few of our components that I expect to be modal, but they aren't. With that in mind, defaulting to modal makes sense to me. That being said, let's get some early feedback from @WordPress/gutenberg-design to ensure we take any early thoughts into consideration when making direction decisions like this. Especially considering that there might be a longer transition period where some components will be modal while others won't be. |
I'm pretty much in line with @ciampo's takes.
I played around with this, and couldn't figure out how it becomes focusable. It's in the DOM but left out of the tab sequence with |
Maybe @diegohaz can help |
The hidden dismiss button added by modal dialogs in Ariakit is designed for screen reader users when a mouse and keyboard are not available (e.g., on mobile/touch devices). They can't click outside or press Esc, but they can access this button. Without this button, they wouldn't be able to close the modal dialog or popover. It's rendered only if the developer does not deliberately render |
I confirmed the StackBlitz demo works as expected on iOS Safari with VoiceOver. Very cool feature, and completely non-intrusive to a user who doesn't need it. Thumbs up from me as well. Thanks @diegohaz! |
Having a dismiss button is most helpful because it makes the action needed to dismiss visible and obvious to users who don't know other methods of exiting, such as I'm opposed to adding a visually hidden dismiss; I think it needs to be visible and always available. Current popover controls have a visible dismiss button, and I'd consider removing that to be a significant accessibility regression; and I don't see any point in adding a visually hidden dismiss button when we have a visible one already. I think our usage of this component should default to including the appropriate DialogDismiss or PopoverDismiss component, but I'm glad that there is at least some fallback if a developer omits those. I'd prefer if it was also available via normal keyboard & screen reader navigation; I'm not sure why it's restricted to mobile interfaces only. @diegohaz can you provide any explanation of that? |
It's not restricted to mobile devices. It's accessible to the virtual cursor on any device or screen reader. Making it accessible to non-screen reader keyboard navigation would require Ariakit to make this button visible, which involves design decisions beyond the library's scope. I agree that always rendering a visible dismiss button is better, and |
Hey @joedolson, I believe that a visible dismiss button is only present in the Here we're discussing modality as a general attribute for all popover-based components. I believe that it would be quite difficult to show a visible dismiss button for all modal popover - based components, and instead we should evaluate the addition of a visual dismiss button on a case by case. For example:
|
Some thoughts.
Selecting this should open a menu, not a dialog. Docs:
While I think this is a great fit for Popovers and modals in general, let's be careful to not throw everything in a modal dialog as we run a high risk of misusing ARIA and miscommunicating what type of interactive controls a widget may have. Thanks. |
Hey @alexstine , good to hear from you! Just to clarify, we are discussing whether we should support modality for popover-based component, what default modality should each component have, and any related aspects (like dismiss buttons). We would not be changing the role of selects or menus to |
@ciampo How are we defining modal here? I believe all dialogs should have visible dismiss buttons, inline dialogs as well as modal dialogs. Thanks. |
I'd agree with @alexstine about the semantics. The Popover component is used for many things though. Some dropdown menus do use a Popover, for example all the dropdown menus in the block toolbar. In this case, the semantics should be 'menu' and should not be changed. Other controls in the editor use a Popover, for example the color picker or the Global styles Shadows editing UI and many others. Typically, these UIs look and behave more like small dialog windows that contain controls or small forms. These aren't menus. They are more non-modal dialogs. Some of them do have an aria-label for the popover container but don't have an ARIA role, which is incorrect and not useful for users. Sine of them do havee a visible title, most don't. See #63899 (comment) How these Popovers should be treated? |
Let's make sure that we're using the same terminology and general concepts. I'm going to quote some content from this excellent article, from MDN, and from the WAI-ARIA APG: popoverA piece of UI (and as of recently, an HTML attribute too) that is not always visible (ie. ephemeral), that is displayed on top of other page content. It can be modal, or non-modal. Popovers don't necessarily come with a built-in role, as they assume many ( In terms of DOM tree, Popovers can be rendered inline next to their anchor, or they can be rendered somewhere else in the DOM (we'd be using React portals in Gutenberg). modalModality is more of a characteristic than a component itself. When a modal component is open, it is the only thing that is not inert. Only the modal content can be interacted with, the rest of the page or application is made inert. Inert content is content that users cannot interact with. It is only really there visually, but you cannot Tab to it, click it, scroll it, or access the content via assistive technologies. dialog (role)The
|
@ciampo Any chance some draft PRs can spin up so I can just test the specific ideas? Sometimes I just don't follow text explanations well without seeing it in practice. |
@alexstine, thanks for your interest in taking a look! @ciampo is on vacation for the next week, but I'm happy to help demonstrate the non-modal behavior in order to keep the discussion going. I've opened a PR here: #64200 - please review that PR for testing instructions. A few notes about this example:
Let me know if you have any questions! |
@tyxla Is there anyway you can add this to something used in the post editor possibly? I'd like to give it a quick test in Playground without having to build it locally. Wish Storybook had a web version to test PRs vs. just latest trunk. Thanks. |
@alexstine Of course! Luckily,
Let me know if you have any other questions. Thanks for testing @alexstine, and I appreciate any feedback you might have! |
@tyxla Thanks for the instructions. Here are my thoughts.
Thanks. |
@alexstine I believe the context is preserved here even when using React Portal, meaning you can tab away from the menu and it works as if it were rendered right after the menu button. The same applies to the virtual cursor (if you disable focus mode while in the menu). There's additional logic to ensure this works. I made this video a while ago to explain the strategy adopted here (sorry, my speech isn't good, but I'm talking about things you're likely already familiar with, so it should be fine to follow): https://www.youtube.com/watch?v=ELauJdPoDaQ If you encounter any issues with this interaction, please feel free to report them. Hopefully, in the near future, we will be able to use the native HTML |
@diegohaz It does not work the same way. Upon following the testing instructions, I can navigate out of the menu by disabling focus mode. The first element I find above the menu in the DOM is the Thanks. |
@alexstine I don't fully understand. Are you able to move the virtual cursor to the notification region, or are you talking about navigating the DOM tree view (not the site UI)? Also, could you please let me know which screen reader and browser you're using? Thanks! I just tested with NVDA and Edge. Either pressing Shift+Tab or Arrow Up with focus mode disabled correctly moves the focus or the virtual cursor to the menu button even though the menu popup is placed at the end of the DOM. Also tested with VoiceOver and Safari. VO+Arrow Left correctly moves the focus to the menu button. |
@diegohaz I am using NVDA and Firefox. If I switch to the virtual cursor (browse mode) pressing up arrow lands on the aria-live region injected by wordpress/a11y. By default, most of the portals I've seen in Gutenberg render near the end of the DOM, not as siblings/descendants of their controls. |
@alexstine I can test it again later. The last time I tested it with NVDA and Firefox, it worked properly. The portal is rendered at the end of the document in the DOM, but it's referenced by |
Here's an audio/video I recorded using NVDA 2024.2 and Firefox 129.0.1 on Windows 11, following the instructions in #64200 In the first part of the video, I use Tab and Shift + Tab to navigate from the menu button to the menu, to the table (the element after the menu button), and back to the menu and the menu button. Then, I use arrow up and arrow down in browse mode to navigate from the menu button to the menu items and back to the menu button. The order of elements in both scenarios seems correct even though the menu is using React Portal to be rendered at the end of the DOM. Parallels.Desktop.mp4 |
Some feedback on a few points from this discussion.
@ciampo, sure and that also means that Popovers must have a role depending on how they are used. When they're used for dialog-like UIs, they should have a role dialog, when they're used for menu-like UIs they whould have a role menu and so on. As such, the Popover component should have a required prop to set an appropriate role and it should not allow implementations without a role.
I agree and that was discussed several times across the yers iin this project. Alas, some designs don't want a dismiss button ad right now there's larte inconsistency in the editor. One exampel of that is in the varous 'welcome' modal dialogs. To me, a dismiss button should be required.
Yes, but the modality also implies that the Popover must have an appropriate ARIA role. I'd like to remind everyone that the concept of 'modality' and thus the aria-modal attirbute can only be used with the
What would be the use case for a dropdown menu that is modal? I'm not sure I've ever seen a menu that makes inert the rest of the page other than in Gutenberg and to me that's an incorrect pattern. Glad it will be revised as mentioned by @tyxla.
Regarding required ARIA roles and attributes, I'd argue that if the components don't make them requried, it is very likely the components will be used incorrectly. This is no different from other long-standing issues like the non-required
Agreed and appreciated. We all want that, not only you as maintainers 🙂
This is a good point from @alexstine. Ideally, any UI that renders in the DOM far from the toggle control that opened it should be treated like a modal dialog. As Alex explains, assistive technologies provide dozens of ways to exit an UI even if tabbing is constrained within that UI. When that happens, users would be totally lost because there's a lot of content in the DOM between the Popover and the toggle that opened it. Which leads us to discuss the 'portal' pattern. It should really be used only for UIs that are modal as in UIs that make all the rest of the page inert. Also the above point has been discussed several times across the years in this project but always dismissed for a reason or another reason. I would like to reinstate once again that for accessibility:
|
@afercia: note that I never said we're necessarily going in that direction. In my comment, I was explaining how the PR works, but as specified in the PR, it was solely for demonstration purposes and getting feedback. The whole idea was to test different ways to set things up and see how we can achieve the best compromise. |
@tyxla Sure, thanks for the clarification 👍🏻 |
Speaking only in the context of mouse/pointer use, it’s nice for dismissing the menu with a hasty click outside because there’s no risk of triggering another action inadvertently. Another way to state it is that for non-modal dropdowns, too much care is often required to avoid some other clickable element when merely intending to dismiss the popover. A tradeoff is that if you want to open another element directly then the first click won’t do it. This is conceivably avoidable though with some technical challenge for the web. An example is Mac OS dropdown menus which are modal in respect to the rest of the interface but adjacent menus open when moving the pointer over their toggles. Such an affordance would be nice at least for some contexts in Gutenberg but as mentioned it may not be easily added. Overall, my intuition is that affording the "safer" pointer dismissal is worth the tradeoff of another click required to activate the next button/link. However, the change may be noticed by and cause annoyance to some so I don’t feel too strongly about it. Debate of which is best aside, it seems more sane to have dropdowns behave consistently so, until all dropdowns could be made modal at once, there shouldn’t be any dropdowns that are modal. I.e. #64200 seems the right thing to do for now. |
Interesting, I didn't know they were modal thanks for pointing that out. In my mind, in a web interface (which isn't directly comparable to an operating system interface), an UI that is modal should also visually communicate that interaction with the rest of the page is prevented. The visual indication of the modality is a key part of a modal UI. Typically, this is done with an overlay with some opacity. As an added bonus, the overlay prevents clicking on any part of the rest of the UI. From a visual perspective, the overlay is a pretty common pattern for modal dialogs. I'm not sure it would be good for a dropdown menu though or any other modal UI that isn't a dialog. |
Out of curiosity, what about native Hence why I was considering making components like Having said that, by exposing the right props (ie. |
Modality is not just about visuals and keyboard interaction. The key parts are that when a modal UI opens, the rest of the page:
To use the example of the Mac OS dropdown menus provided by @stokesman, when I open one of the menus in the macOS toolbar VoiceOver doesn't perceive any other content in the page. The same thing happens with native select elements. I don't know whether that happens also on Windows. Anyways, I see that as an operating system pattern as also native select element are highly dependent on the operating system. Instead, I would like to remind everyone that the aria-modal attribute can only be used on UIs with a role of window, dialog, or alertdialog. Which hints that a modal behavior can be used only on those patterns. An entirely different matter is constraining tabbing or implementing only some parts of a modal behavior on other kind of UIs. |
Yup, I'm aware of how inertness and modality are related (I also linked to this article in this issue's first message). What I'm trying to say is: if the native Are you saying that modality of native components varies from OS to OS, and therefore we can't assume that all native |
Honestly I don't know. It should be tested. But I don't think it's worth it. As I mentioned earlier, the |
Context
@wordpress/components
: portal behavior of popover-based components #63697As we're re-writing some of the components in the
@wordpress/components
package to useariakit
, we have the chance to re-discuss our approach to architecting our APIs and the behavior of such components.More specifically, as we're working on the new version of
DropdownMenu
andCustomSelectControl
, one of the aspects that we should discuss and agree on at a package level is the modality of popover-based components.Using
ariakit
allows us to easily switch the modality (and related behaviour) of popover-based components (popover, tooltips, dropdowns, dropdown menus, dialogs, selects, comboboxes...) via a few props.Note
Making a popover "modal" means that the rest of the page should be considered inert (inaccessible to assistive technology and non-interactable by the user while the popover is open, as explained well in this article).
In
ariakit
there are some related aspects that come with modality (and can be customized to a degree):document.body
, while non-modal popovers are rendered inlinedocument.body
h1
What
Therefore, we need to decide:
Popover
from this package?cc @WordPress/gutenberg-components
The text was updated successfully, but these errors were encountered: