Skip to content
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

[Snackbar] Accessibility - Keyboard focus should move to the Snackbar when it opens #22354

Closed
2 tasks done
ahayes91 opened this issue Aug 25, 2020 · 13 comments
Closed
2 tasks done
Labels
accessibility a11y component: snackbar This is the name of the generic UI component, not the React module!

Comments

@ahayes91
Copy link

ahayes91 commented Aug 25, 2020

  • The issue is present in the latest release.
  • I have searched the issues of this repository and believe that this is not a duplicate.

Current Behavior 😯

When you activate a snackbar toast and the contents of the toast are announced to the user, the focus should also move to the toast. It seems like the focus is lost at the moment / it is unclear where exactly the keyboard focus is when the toast first opens. When the toast is closed, focus should return to the original element that opened the toast (if it is still present on the page).

Expected Behavior 🤔

When you activate a snackbar toast and the contents of the toast are announced to the user, the focus should also move to the first interact-able element of the toast (like the action or close buttons). When you close the toast, focus should return back to the element that opened the toast.

While there are no specific aria guidelines for toasts, a modal dialog is probably the closest example:
https://www.w3.org/TR/wai-aria-practices-1.1/examples/dialog-modal/alertdialog.html
We've also been given this feedback from our users.

Steps to Reproduce 🕹

Steps:

  1. Open up https://material-ui.com/components/snackbars/#simple-snackbars (Turn on VoiceOver screenreader if you want to hear announcements)
  2. Using keyboard only, navigate to "Open Simple Snackbar" button and activate it with space or enter keys.
  3. Although the contents of the toast are announced if you have your screenreader turned on, it is unclear where the keyboard focus is currently.
  4. Hitting the tab key will bring you to the first focusable element of the toast.

Context 🔦

Our keyboard-only users expect to be able to keep track of focus as they interact with elements that fire toasts.

Your Environment 🌎

Tech Version
Material-UI Latest as per docs
MacOS Catalina 10.15.6
Browser Chrome Version 84.0.4147.135 (Official Build) (64-bit)
@ahayes91 ahayes91 added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Aug 25, 2020
@eps1lon eps1lon added accessibility a11y component: snackbar This is the name of the generic UI component, not the React module! and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Aug 25, 2020
@eps1lon
Copy link
Member

eps1lon commented Aug 25, 2020

We should be very careful with making this the default behavior. For UIs using the snackbar for notifications (possible displaying multiple) you don't want the Snackbar to steal focus.

I don't think Snackbar should be used as an alertdialog due to their positioning.

In the case of the Snackbar with an undo action it's probably better to add a keyboard shortcut. Otherwise it might be disruptive if you trigger an action and every time the focus is moved to an option "undo" element.

So there's a lot of nuance to this component. We didn't do a good job making it accessible by default especially due to the poor naming and customization points without oversight.

Some brainstorming on how to recommend better a11y (without having looked into prior art for accessible notifications/undo popups):

  1. don't move focus if action is provided but provide some helper method that creates accessible actions (with shortcuts)
  2. add option to move focus
    I'm not convinced yet that we need it. A concrete example of a Snackbar where this should happend would help deciding what to do about it.
  3. Due to its position portaling it seems ideal. Sequential focus order should follow the visual order so it would be disorienting if it would appear at the bottom visually but in the middle of the main content in tab order.

@ahayes91
Copy link
Author

ahayes91 commented Aug 25, 2020

We should be very careful with making this the default behavior. For UIs using the snackbar for notifications (possible displaying multiple) you don't want the Snackbar to steal focus.

I didn't think of a scenario where Snackbar was used for notifications, I see your point. However, in the case of Snackbar appearing after a user interaction, I think focus moving to the Snackbar could still be a valid scenario.

Even if we decide not to implement moving the focus to the Snackbar when it's been opened, in any case there should probably be better visual indications of where the focus is after closing a Snackbar (on https://material-ui.com/components/snackbars/#simple-snackbars, after you close the Snackbar you have no visual indicators as to where your focus is - it seems like focus is lost when the Snackbar is removed from the DOM).

I don't think Snackbar should be used as an alertdialog due to their positioning.

Any chance you could expand on this? Our use case would be one such as showing an alert dialog as a toaster after a user interaction (say, clicking a button to submit a form and having a dismissible "Success!" toaster appear on screen).

In the case of the Snackbar with an undo action it's probably better to add a keyboard shortcut. Otherwise it might be disruptive if you trigger an action and every time the focus is moved to an option "undo" element.

I agree, maybe my ask for the focus should move automatically to the "X" button of the snackbar instead of the first interact-able element.

Some brainstorming on how to recommend better a11y (without having looked into prior art for accessible notifications/undo popups):

  1. don't move focus if action is provided but provide some helper method that creates accessible actions (with shortcuts)

It's late in the day for me so you'll have to forgive me for not following this one 😂 - how would this work from a user perspective?

  1. add option to move focus
    I'm not convinced yet that we need it. A concrete example of a Snackbar where this should happend would help deciding what to do about it.
  2. Due to its position portaling it seems ideal. Sequential focus order should follow the visual order so it would be disorienting if it would appear at the bottom visually but in the middle of the main content in tab order.

Portaling, if I understand correctly, would render the toaster in a different area of the DOM like in https://material-ui.com/components/portal/ - that seems reasonable but in that case I would definitely want to see focus moved to the toaster after a user interaction - otherwise I'm wondering would a keyboard-only/screenreader user have a hard time "finding" the snackbar on the page?

@eps1lon I really appreciate your thoroughness on this! A11y is so subjective - it's hard to know exactly what the right approach is.
PS I'll have a chat with some other a11y heads I know and will update here if I change my mind again 😂

@eps1lon
Copy link
Member

eps1lon commented Aug 25, 2020

Any chance you could expand on this? Our use case would be one such as showing an alert dialog as a toaster after a user interaction (say, clicking a button to submit a form and having a dismissible "Success!" toaster appear on screen).

That's not an alertdialog as it's defined in the ARIA authoring practices:

An alert dialog is a modal dialog that interrupts the user's workflow to communicate an important message and acquire a response.

-- https://www.w3.org/TR/wai-aria-practices-1.1/#alertdialog

ARIA is not as clear. A snackbar can definitely act as an alertdialog where you want the user to confirm but I don't believe this is the best UI for this type of notification due to its placement. Using it as an alertdialog is actually less accessible for sighted users I'd say.

it's hard to know exactly what the right approach is.

Definitely. It's why having concrete examples is so important when filing issues. It also reveals problems with a component if it can be used for too many UI patterns. Then it fails to provide real value to developers if they have to customize it before it actually works.

We already improved in that area by implementing a standalone Alert.

However, in the case of Snackbar appearing after a user interaction, I think focus moving to the Snackbar could still be a valid scenario.

At the top of my had I can't recall any toast-like element that should steal focus. A popular use case where you have it as a confirmation with an action to undo can be accessible by providing a different shortcut to undo e.g CTRL + Z which acts like onClick on the undo action. ESC would act like onClick for the dismiss button. The problem with focus stealing is that it is interrupting the workflow and you should have a strong reason for doing so. A confirmation message does not fall into that category in my opinion.

It's late in the day for me so you'll have to forgive me for not following this one joy - how would this work from a user perspective?

Basically something like <Snackbar action={<ShortcutButton shortcut="CTRL+Z" onClick={handleClick} />} . ShortcutButton would not be in sequential focus order but register a global keydown listener for CTRL + Z that calls handleClick.

@mbrookes
Copy link
Member

mbrookes commented Aug 25, 2020

It's probably worth revisiting what the Snackbar's intended use is:

https://material.io/components/snackbars#usage

When to use

Snackbars communicate messages that are minimally interruptive and don’t require user action.

Component Priority User action
Snackbar Low priority Optional: Snackbars disappear automatically
Banner Prominent, medium priority Optional: Banners remain until dismissed by the user, or if the state that caused the banner is resolved
Dialog Highest priority Required: Dialogs block app usage until the user takes a dialog action or exits the dialog (if available)

@ahayes91
Copy link
Author

Thanks @mbrookes @eps1lon that all makes sense - I think Snackbar is the appropriate component for our "success" message use case but you're right, it doesn't need to get focus automatically because it doesn't require interaction from the user.
The only thing we would need to clarify is what should happen to the focus if a user does manage to keyboard-navigate to the snackbar and close it before it fades away. At the moment, the focus disappears. Can raise a separate issue for that and close this one if need be?

@oliviertassinari
Copy link
Member

oliviertassinari commented Aug 26, 2020

@mbrookes So it would support the idea that the Snackbar should be portal, so it can't be reached with Tab?

To be fair, I believe that most applications use the Snackbar with a global dispatch system anyway (e.g. redux). So in practice, the Snackbar is most likely not reachable with Tab unless reaching the end (most likely) or the beginning of the page (depending on how it was set up). I have been advocating for something similar in #21053 (comment).


Regarding:

Keyboard focus should move to the Snackbar when it opens

Should it be deferred to the userland? Not something Material-UI handles in the core?

The only thing we would need to clarify is what should happen to the focus if a user does manage to keyboard-navigate to the snackbar and close it before it fades away.

As focus moves back to the body? What other behavior did you envision?

@eps1lon
Copy link
Member

eps1lon commented Aug 26, 2020

As focus moves back to the body? What other behavior did you envision?

One that doesn't interrupt the current workflow.

@mbrookes
Copy link
Member

So it would support the idea that the Snackbar should be portal[led], so it can't be reached with Tab?

I'm not so sure... User interaction is optional, but there may be an action the user wants to take, such as "Undo". What does a keyboard user do in that scenario if they can't focus the button?

@eps1lon
Copy link
Member

eps1lon commented Aug 27, 2020

What does a keyboard user do in that scenario if they can't focus the button?

Have a global shortcut which has a faster workflow for power users since you don't have to TAB and ENTER just hit a single combination:

Basically something like <Snackbar action={} . ShortcutButton would not be in sequential focus order but register a global keydown listener for CTRL + Z that calls handleClick.

It's really hard to discuss this without user testing. It isn't clear to me if putting it in tab order isn't disrupting or if users even understand what's going on if they tab into an "undo" action that wasn't there before.

@TheWilks
Copy link

Part of the problem is also that the Material UI snackbars read the buttons when using a screen reader unlike the Material IO demo (https://material-components.github.io/material-components-web-catalog/#/component/snackbar). If the component is changed to not read the buttons and you include the shortcut text for screen readers only as mentioned above then it would solve the problem.

I also identified some issues with a variety of screen reader / browser combos that seem to be resolved if you move the role=alert to the text of the snackbar (#29080).

@eps1lon
Copy link
Member

eps1lon commented Nov 22, 2021

One thing I want to note really quickly is that browsers do have an internal focus cursor that we don't have access to with DOM APIs and that may cause disorienting behavior with portals i.e. our current Snackbar implementation.

Say you have a document with 3 Buttons:

  <button />
  <button />
  <button />

Now we tab to the second button (-> indicates the focus cursor):

   <button />
-><button />
   <button />

Now we remove document.activeElement i.e. the second button. While document.activeElement will point to document.body, the focus cursor will remain at this "point" in the DOM:

   <button />
->
   <button />

If we TAB again, the focus cursor moves to the next element i.e. document.activeElement will become the next <button />

   <button />

-><button />

So moving focus to the Snackbar may result in a focus cursor that is no longer in the list if the previously focus element is removed when the snackbar opens. For example, take list of TODO items with a remove action (ListItem is li > button):

   <ListItem>A</ListItem>
   <ListItem>B</ListItem>
   <ListItem>C</ListItem>

We TAB to the second item:

   <ListItem>A</ListItem>
-><ListItem>B</ListItem>
   <ListItem>C</ListItem>

and remove it:

  <ListItem>A</ListItem>
->
  <ListItem>C</ListItem>

which prompts a Snackbar asking us to undo

   <ListItem>A</ListItem>
   <ListItem>C</ListItem>
   <Snackbar>
->  <Undo />
   </Snackbar>

So now we have to handle focus restoration depending on whether we undo or not. If we just blur the undo button we land at the end of document since this is were the undo button was located:

   <ListItem>A</ListItem>
   <ListItem>C</ListItem>
->

I have a small codesandbox illustrating the issue: https://codesandbox.io/s/move-focus-to-snackbar-focus-cursor-issue-btsbv

The only leverage we have is in the case of undo but the Snackbar would not be able to restore since the restored ListItem would be a new HTMLButtonElement.

The correct solution would be to keep <ListItem>B</ListItem> in the DOM until we don't have the ability to remove it anymore. Though this gets increasingly complicated if you have previous actions in a stack to be able to undo multiple actions.
Not using a portal is also a possible solution but results in footguns relating to stacking context.

What my concern is that this behavior isn't trivial to implement and the problem itself is most likely not obvious to most people since it's "only" a keyboard related issues.

We can avoid this footgun entirely if we don't move focus around and add an affordance to "undo" (or whatever action you have in your Snackbar) with a separate keyboard combination.

Alternatively we hijack tabbing behavior enitrely and re-implement a focus cursor that considers the React tree not DOM tree.

I hope this helps illustrating why moving focus to the Snackbar is potentially dangerous. If I left anything unclear, please comment on what's missing or unclear.

@eps1lon
Copy link
Member

eps1lon commented Nov 23, 2021

Just noticed that Snackbar isn't portaled. So the problem doesn't exist right now. But there is considerable desire to allow for imperative toasts that would require a single Snackbar instance that will probably be portaled so we need to consider this in #18098.

@eps1lon
Copy link
Member

eps1lon commented Nov 23, 2021

Note that you can already implement this behavior with

<Snackbar
  open={open}
  onClose={handleClose}
  message="Note archived"
  action={<button autoFocus>UNDO</button>}
/>;

Considering this behavior is already implementable and making this the default behavior is potentially interruptive, I'm closing this issue for now.

I don't see how we can safely do the right thing automatically. What do we do with multiple actions? How do we restore focus if the element triggering the Snackbar got removed?

It would help a lot if we could gather use cases where focus should be moved so that we may be able to create more specific components. I just don't see compelling evidence for moving focus into a generic "toast" component.

@eps1lon eps1lon closed this as completed Nov 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accessibility a11y component: snackbar This is the name of the generic UI component, not the React module!
Projects
None yet
Development

No branches or pull requests

5 participants