[BUG][EuiModal] Fix VoiceOver + Safari escaping focus trap#7564
[BUG][EuiModal] Fix VoiceOver + Safari escaping focus trap#75641Copenut merged 11 commits intoelastic:mainfrom 1Copenut:feature/euiModal-a11y-markup
Conversation
1Copenut
left a comment
There was a problem hiding this comment.
Two clarifying comments into my "why".
src/components/modal/modal.tsx
Outdated
| * Identifies a modal dialog to screen readers. Modal dialogs that confirm destructive actions | ||
| * or need a user's attention should use "alertdialog". | ||
| */ | ||
| ariaRole?: 'dialog' | 'alertdialog'; |
There was a problem hiding this comment.
I added this b/c there are a handful of cases where I'd like the modal to break user flow and announce itself immediately. Thinking specifically about modals that confirm deleting or removing an object here. By adding it as a new prop and setting a standard default, I thought we could capture the best of both worlds without accidentally regressing the container to a div[role="presentation"].
There was a problem hiding this comment.
Just curious, is there a specific reason this needs to be named ariaRole or can we just go with role to match the DOM attribute?
There was a problem hiding this comment.
Also, bonus/optional question: is there ever a situation where consumers would need to unset the role or set a non dialog role? I'm leaning towards no, but just wanted to at least raise the question and get people's thoughts
There was a problem hiding this comment.
I added this b/c there are a handful of cases where I'd like the modal to break user flow and announce itself immediately. Thinking specifically about modals that confirm deleting or removing an object here
Should we should go ahead and add this prop to EuiConfirmModal as well with a default of alertdialogue?
There was a problem hiding this comment.
No specific reason to name it ariaRole. I was thinking role at first but talked myself out of it. I like your point that role is the HTML attribute name, and changing to that feels fitting.
I also like the suggestion to make EuiConfirmModal default to alertdialog. I'll update and retest those.
There was a problem hiding this comment.
I went ahead and added aria-label attributes to each example in docs. The axe-core plugin was emphasizing elements with role="dialog | alertdialog" should have accessible labels. Ideally we enforce having an EuiModalHeader and title in each modal, but that's increasing scope more than the original PR set out to do.
| if (isModalVisible) { | ||
| modal = ( | ||
| <EuiConfirmModal | ||
| aria-label="EuiModal confirm example" |
There was a problem hiding this comment.
I'm confused about the aria-label examples that you added. These don't feel like useful production examples to me that a developer would be able to extrapolate from and write something themselves for their own modals.
- Why would the aria-label for the modal be different from the title? Should we not use
aria-labelledbyinstead and point that at the title? - If the modal label should be different from the title, then let's make the copy actually specific to the modal intent. e.g., for this one, something like
Confirm subscription
There was a problem hiding this comment.
Ideally the accessible label uses the title and an ID to create an aria-labelledby situation. Looking at the code as it sits, we can't count on a title always being present, at least not in the basic EuiModal. The confirmation modal is a bit easier to reason about where we're checking if the title prop is present.
I'm at a crossroads what is the best approach here. I can either keep this one scoped tightly to fix the immediate problem of VoiceOver escaping the modal and make a follow on item to improve accessible labels, or widen the scope of this issue and possibly it becomes a breaking change. Either seems an acceptable path forward.
There was a problem hiding this comment.
When in doubt, always keep the scope smaller :)
|
I refactored the examples to use explicit IDs and |
- already inherited from `HTMLAttributes`
|
Preview staging links for this PR:
|
💚 Build Succeeded
History
cc @1Copenut |
|
UPDATE March 12: Thank you @cee-chen. I'll give this one more pass with screen readers in the morning and merge. |
`v93.3.0`⏩ `v93.4.0` --- ## [`v93.4.0`](https://github.com/elastic/eui/releases/v93.4.0) - Added the following properties to `EuiButtonGroup`'s `options` configs: `toolTipContent`, `toolTipProps`, and `title`. These new properties allow wrapping buttons in `EuiToolTips`, and additionally customizing or disabling the native browser `title` tooltip. ([#7461](elastic/eui#7461)) - Enhanced `EuiResizeObserver` and `useResizeObserver`'s performance to not trigger page reflows on resize event ([#7575](elastic/eui#7575)) - Updated `EuiSuperUpdateButton` to support custom button text via an optional `children` prop ([#7576](elastic/eui#7576)) **Bug fixes** - Fixed `EuiFlyout` to not repeatedly remove/add a body class on resize ([#7462](elastic/eui#7462)) - Fixed `EuiToast` title text to wrap instead of overflowing out of the container ([#7568](elastic/eui#7568)) - Fixed a visual bug with `EuiHeaderBreadcrumbs` with popovers ([#7580](elastic/eui#7580)) **Deprecations** - Deprecated `euiPalettePositive` and `euiPaletteNegative` in favour of a more culturally inclusive `euiPaletteGreen` and `euiPaletteRed` ([#7570](elastic/eui#7570)) - Deprecated all charts theme exports in favor of `@elastic/charts` exports: ([#7572](elastic/eui#7572)) - Deprecated `EUI_CHARTS_THEME_<DARK|LIGHT>` in favor of `<DARK|LIGHT>_THEME` from `@elastic/charts`. ([#7572](elastic/eui#7572)) - Deprecated `EUI_SPARKLINE_THEME_PARTIAL` in favor of `useSparklineOverrides` theme from the kibana `charts` plugin `theme` service. **Accessibility** - Updated `EuiModal` to set an `aria-modal` attribute and a default `dialog` role ([#7564](elastic/eui#7564)) - Updated `EuiConfirmModal` to set a default `alertdialog` role ([#7564](elastic/eui#7564)) - Fixed `EuiModal` and `EuiConfirmModal` to properly trap Safari+VoiceOver's virtual cursor ([#7564](elastic/eui#7564))
Summary
I've recently noticed I'm able to escape the
EuiModalfocus trap to the inert page when using VoiceOver + Safari. This only happens when I'm navigating by virtual cursor chordsCtrl + Opt + ARROW_KEY, but this is a primary navigation pattern so it needed to be remediated.PR closes #7532 and closes #7563.
QA
Remove or strikethrough items that do not apply to your PR.
General checklist
@defaultif default values are missing) and playground togglesIf applicable, added the breaking change issue label (and filled out the breaking change checklist)Screen reader test pairings
All pairings trapped focus correctly. This represents an improved UX for Safari + VO, and no regression for the other pairings.