From 1ce38812d860ad823414f2f0f35c02340855eb8d Mon Sep 17 00:00:00 2001 From: Melanie Sumner Date: Fri, 18 Jan 2019 11:14:17 -0600 Subject: [PATCH 01/11] Create 0000-a11y-routing.md RFC to help routes be accessible in Ember --- text/0000-a11y-routing.md | 91 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 text/0000-a11y-routing.md diff --git a/text/0000-a11y-routing.md b/text/0000-a11y-routing.md new file mode 100644 index 0000000000..3645507b18 --- /dev/null +++ b/text/0000-a11y-routing.md @@ -0,0 +1,91 @@ +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: (leave this empty) +- Ember Issue: (leave this empty) + +# Accessible Routing in Ember + +## Summary + +When the user navigates to a new route within an Ember application, screen readers do not read out the new content or appropriately move focus. This makes Ember applications not natively accessible for users with assistive technology. + +Ember needs to provide a machine-readable way for assistive technology (like screen readers) to know that the route transition has occurred. + +## Motivation + +In a world where accessible websites are increasingly the norm, it becomes good business to ensure that Ember applications are able to reach basic accessibility standards. The first initiative in this area is to ensure that Ember can provide the same level of accessibility as a static website- and the first task is to tackle basic navigation within a web app. + +In a static website, checking `document.activeElement` in the console upon navigation to a new page should return `[object HTMLBodyElement]`. However, in an Ember app this is not the case. For example, if you navigate to one of the pages via the navbar, the focus remains on the element in the navbar. This is a problem for accessibility; the screen reader doesn’t know that a route transition has occurred, and the screen reader user doesn’t know that the link to the new page actually worked (they hear nothing). + +So we have a puzzle to figure out: why don't screen readers read out the new content or appropriately move focus when the user navigates to a new route within an Ember application. In researching the issue more thoroughly, we discovered that NVDA doesn't seem to have a way to handle the history API. (Which makes sense- screen readers were released before the history API was released.) + +One of the challenges is that screen reader technology is all closed-source except for [NVDA](https://www.nvaccess.org/), the open source screen reader for Windows (and typically used with Firefox). The source code for NVDA is available on [GitHub](https://github.com/nvaccess/nvda/) and is written in Python. It seems useful, at least initially, to try to solve for SPAs + NVDA, since both can be done in open source. (Related: [NVDA Issue #6606](https://github.com/nvaccess/nvda/issues/6606)) + +## Detailed design + +After reviewing possible approaches to this problem, it seems like the very first step should be an implementation that, at the very _least_, returns Ember back to the starting line. We could then build on that by providing a more machine-intelligent iteration for an upscaled experience. This iterative approach would give us a better way to tackle the more difficult edge-case scenarios. + +This RFC proposes that we could either implement a two-phase approach (outlined below) OR decide to move directly to the approach outlined in phase two. + +### Phase One - Return to the starting line + +This solution proposes the simplest possible solution- implement in Ember what already exists in other traditional websites: +1. When a browser loads a website, the screen reader starts to read the content of that page +2. When the user navigates to a new page in that website, the screen reader starts to read the content of that page +3. (repeat) + +Perhaps we could achieve this by adding a function to set focus on the body element when a route transition has occurred. + +In order to not break similar solutions that may already exists in other apps or addons, I propose that the solution be implemented as an optional feature/with a flag. The default could be set to on, and if folks already have a solution in their addons/apps, they could then opt-out of this feature. + +### Phase Two - Intelligent content focus + +It would be even better to have a more intelligent solution, since there are several ways new page content could be introduced in an Ember application. + +[Ember-self-focused](https://github.com/linkedin/self-focused/tree/master/packages/ember-self-focused) and [ember-a11y](https://github.com/ember-a11y/ember-a11y) are both examples of community-produced addons that attempt to provide a contextual focus, based on new page content. + +From the [Ember-self-focused](https://github.com/linkedin/self-focused/tree/master/packages/ember-self-focused) readme: +> When UI transitions happen in a SPA (or in any dynamic web application), there is visual feedback; however, for users of screen reading software, there is no spoken feedback by default. Traditionally, screen reading software automatically reads out the content of a web page during page load or page refresh. In single page applications, the UI transitions happen by rewriting the current page, rather than loading entirely new pages from a server; this makes it difficult for screen reading software users to be aware of UI changes (e.g., clicking on the navigation bar to load a new route). + +> If the corresponding HTML node of the dynamic content can be focused programmatically, screen reading software will start speaking the textual content of that node. Focusing the corresponding HTML node of the dynamic content can be considered guided focus management. Not only will it facilitate the announcement of the textual content of the focused HTML node, but it will also serve as a guided context switch. Any subsequent “tab” key press will focus the next focusable element within/after this context. + +The tedious, error-prone task of keeping track of HTML nodes that can/could/would/should have focus is something that Ember could do for you. + +A comparable from another JS Ecosystem: [Reach Router](https://reach.tech/router) is available for React apps. + +## How we teach this + +For phase one, the guides should include information about skip links, since the application author will need to add those in themselves (just as they would for a static site). For phase two, we would explain that skip links are no longer needed because we have integrated that functionality in a more machine intelligent way. + +### Skip Links +A skip link is an internal link at the beginning of a hypertext document that permits users to skip navigational material and quickly access the document's main content. Skip links are particularly useful for users who access a document with screen readers and users who rely on keyboards. + +Directly inside the `body` element, place a skip link like this: + + Skip to main content + +This allows the user with assistive technology to skip directly to the main content and is useful for not needing to repeatedly navigate through a navbar, for example. + + +It's possible that this solution is a [spherical cow](https://en.wikipedia.org/wiki/Spherical_cow), and the complexity of route transitions in a framework deserves an accessibility solution that is is equally complex. + +It's possible that enterprise Ember users have already implemented their own solution, since governments in many countries require this by law. An out of the box solution would need to be well-advertised and documented to avoid any potential conflicts for users. + +## Alternatives +1. We could try to implement something like Apple’s first responder pattern: https://developer.apple.com/documentation/uikit/uiresponder/1621113-becomefirstresponder +2. Wait for a SPA solution to be implemented in assistive technology. I would note that this option seems less than ideal, since this specific issue has been reported to the different assistive technologies available, and has been an on-going issue for many years now. + +## Unresolved Questions +- how would we handle loading states? +- how does/could/would this fit in with the router helpers work that Chad did? + +## Appendix + +### Related RFCs +- [Route Helpers](https://emberjs.github.io/rfcs/0391-router-helpers.html) by Chad Hietala + +### Related Reading + +- [NVDA Developer Guide](https://www.nvaccess.org/files/nvda/documentation/developerGuide.html) +- [Microsoft Active Accessibility (MSAA)](https://docs.microsoft.com/en-us/windows/desktop/winauto/microsoft-active-accessibility) MSAA is an Application Programming Interface (API) for user interface accessibility. MSAA was introduced as a platform add-on to Microsoft Windows 95 in 1997. MSAA is designed to help Assistive Technology (AT) products interact with standard and custom user interface (UI) elements of an application (or the operating system), as well as to access, identify, and manipulate an application's UI elements. The current and latest specification of MSAA is found in part of [Microsoft UI Automation](https://docs.microsoft.com/en-us/windows/desktop/WinAuto/uiauto-specandcommunitypromise) Community Promise Specification. +- [Structured Negtotiation: A Winning Alternative to Lawsuits](https://www.lflegal.com/book/) + From c666a301f66b00f36f8a601c3e92291526b8bfed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Clemens=20M=EF=BF=BDller?= Date: Thu, 24 Jan 2019 10:10:43 -0600 Subject: [PATCH 02/11] Update text/0000-a11y-routing.md Co-Authored-By: MelSumner --- text/0000-a11y-routing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-a11y-routing.md b/text/0000-a11y-routing.md index 3645507b18..c23e1dbecd 100644 --- a/text/0000-a11y-routing.md +++ b/text/0000-a11y-routing.md @@ -87,5 +87,5 @@ It's possible that enterprise Ember users have already implemented their own sol - [NVDA Developer Guide](https://www.nvaccess.org/files/nvda/documentation/developerGuide.html) - [Microsoft Active Accessibility (MSAA)](https://docs.microsoft.com/en-us/windows/desktop/winauto/microsoft-active-accessibility) MSAA is an Application Programming Interface (API) for user interface accessibility. MSAA was introduced as a platform add-on to Microsoft Windows 95 in 1997. MSAA is designed to help Assistive Technology (AT) products interact with standard and custom user interface (UI) elements of an application (or the operating system), as well as to access, identify, and manipulate an application's UI elements. The current and latest specification of MSAA is found in part of [Microsoft UI Automation](https://docs.microsoft.com/en-us/windows/desktop/WinAuto/uiauto-specandcommunitypromise) Community Promise Specification. -- [Structured Negtotiation: A Winning Alternative to Lawsuits](https://www.lflegal.com/book/) +- [Structured Negotiation: A Winning Alternative to Lawsuits](https://www.lflegal.com/book/) From 7fdf91d8f1e0be20a5f6d5a28a95f3f36b50f8cb Mon Sep 17 00:00:00 2001 From: Melanie Sumner Date: Sun, 3 Mar 2019 17:19:57 -0600 Subject: [PATCH 03/11] Update 0000-a11y-routing.md updated related references with a more complete list of accessibility APIs. --- text/0000-a11y-routing.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/text/0000-a11y-routing.md b/text/0000-a11y-routing.md index c23e1dbecd..e5bfab5c6b 100644 --- a/text/0000-a11y-routing.md +++ b/text/0000-a11y-routing.md @@ -84,8 +84,11 @@ It's possible that enterprise Ember users have already implemented their own sol - [Route Helpers](https://emberjs.github.io/rfcs/0391-router-helpers.html) by Chad Hietala ### Related Reading - +- Accessibility APIs + - Windows: [Microsoft Active Accessibility](https://docs.microsoft.com/en-us/windows/desktop/WinAuto/microsoft-active-accessibility) (MSAA), extended with another API called [IAccessible2](https://wiki.linuxfoundation.org/accessibility/iaccessible2/start) (IA2) + - Windows: [UI Automation](https://docs.microsoft.com/en-us/windows/desktop/WinAuto/entry-uiauto-win32) (UIA), the Microsoft successor to MSAA. A browser on Windows can choose to support MSAA with IA2, UIA, or both. + - MacOS: [NSAccessibility](https://developer.apple.com/documentation/appkit/nsaccessibility) (AXAPI) + - Linux/Gnome: [Accessibility Toolkit](https://developer.gnome.org/atk/stable/) (ATK) and [Assistive Technology Service Provider Interface](https://developer.gnome.org/libatspi/stable/) (AT-SPI). This case is a little different in that there are actually two separate APIs: one through which browsers and other applications pass information along to (ATK) and one that ATs then call from (AT-SPI). - [NVDA Developer Guide](https://www.nvaccess.org/files/nvda/documentation/developerGuide.html) -- [Microsoft Active Accessibility (MSAA)](https://docs.microsoft.com/en-us/windows/desktop/winauto/microsoft-active-accessibility) MSAA is an Application Programming Interface (API) for user interface accessibility. MSAA was introduced as a platform add-on to Microsoft Windows 95 in 1997. MSAA is designed to help Assistive Technology (AT) products interact with standard and custom user interface (UI) elements of an application (or the operating system), as well as to access, identify, and manipulate an application's UI elements. The current and latest specification of MSAA is found in part of [Microsoft UI Automation](https://docs.microsoft.com/en-us/windows/desktop/WinAuto/uiauto-specandcommunitypromise) Community Promise Specification. - [Structured Negotiation: A Winning Alternative to Lawsuits](https://www.lflegal.com/book/) From f34a2a792bb7223d2fed0662681aa2a7cc61b688 Mon Sep 17 00:00:00 2001 From: Melanie Sumner Date: Mon, 13 May 2019 13:43:30 -0500 Subject: [PATCH 04/11] Update 0000-a11y-routing.md Lots of updates, technical solutions included. --- text/0000-a11y-routing.md | 126 +++++++++++++++++++++++++++++--------- 1 file changed, 97 insertions(+), 29 deletions(-) diff --git a/text/0000-a11y-routing.md b/text/0000-a11y-routing.md index e5bfab5c6b..cdb8230302 100644 --- a/text/0000-a11y-routing.md +++ b/text/0000-a11y-routing.md @@ -1,4 +1,4 @@ -- Start Date: (fill me in with today's date, YYYY-MM-DD) +- Start Date: (YYYY-MM-DD, updated on 2019-05-13) - RFC PR: (leave this empty) - Ember Issue: (leave this empty) @@ -22,66 +22,134 @@ One of the challenges is that screen reader technology is all closed-source exce ## Detailed design -After reviewing possible approaches to this problem, it seems like the very first step should be an implementation that, at the very _least_, returns Ember back to the starting line. We could then build on that by providing a more machine-intelligent iteration for an upscaled experience. This iterative approach would give us a better way to tackle the more difficult edge-case scenarios. +This RFC proposes two different solutions for us to consider. The solution that is determined to be the most appropriate for our use will be made into an officially supported addon, and be included in the Ember blueprints, so it will be part of every new application by default. -This RFC proposes that we could either implement a two-phase approach (outlined below) OR decide to move directly to the approach outlined in phase two. +### Solution #1 - Navigation Message -### Phase One - Return to the starting line +> "When you learn how to use a screen reader, the first thing you learn how to do, is stop it talking." - a screen reader user. -This solution proposes the simplest possible solution- implement in Ember what already exists in other traditional websites: -1. When a browser loads a website, the screen reader starts to read the content of that page -2. When the user navigates to a new page in that website, the screen reader starts to read the content of that page -3. (repeat) +One potential solution is that we don't overwhelm the screen reader user with the contents of the page, but rather inform them that they have successfully transitioned to their desired route. In doing so, we will also reset the focus for them. -Perhaps we could achieve this by adding a function to set focus on the body element when a route transition has occurred. +- A component is created that provides a screen-reader only message, informing the user that "Page transition is complete. You may now navigate as you wish." The text of this component could be internationalized. +- Focus is reset to the top left of the page -In order to not break similar solutions that may already exists in other apps or addons, I propose that the solution be implemented as an optional feature/with a flag. The default could be set to on, and if folks already have a solution in their addons/apps, they could then opt-out of this feature. +The component template: -### Phase Two - Intelligent content focus +```hbs + +``` -It would be even better to have a more intelligent solution, since there are several ways new page content could be introduced in an Ember application. +The component js: -[Ember-self-focused](https://github.com/linkedin/self-focused/tree/master/packages/ember-self-focused) and [ember-a11y](https://github.com/ember-a11y/ember-a11y) are both examples of community-produced addons that attempt to provide a contextual focus, based on new page content. +```js +import Component from '@ember/component'; +import layout from '../templates/components/navigation-narrator'; +import { inject as service } from '@ember/service'; +import { schedule } from '@ember/runloop'; -From the [Ember-self-focused](https://github.com/linkedin/self-focused/tree/master/packages/ember-self-focused) readme: -> When UI transitions happen in a SPA (or in any dynamic web application), there is visual feedback; however, for users of screen reading software, there is no spoken feedback by default. Traditionally, screen reading software automatically reads out the content of a web page during page load or page refresh. In single page applications, the UI transitions happen by rewriting the current page, rather than loading entirely new pages from a server; this makes it difficult for screen reading software users to be aware of UI changes (e.g., clicking on the navigation bar to load a new route). +export default Component.extend({ + layout, + tagName: '', + router: service(), -> If the corresponding HTML node of the dynamic content can be focused programmatically, screen reading software will start speaking the textual content of that node. Focusing the corresponding HTML node of the dynamic content can be considered guided focus management. Not only will it facilitate the announcement of the textual content of the focused HTML node, but it will also serve as a guided context switch. Any subsequent “tab” key press will focus the next focusable element within/after this context. + init() { + this._super(); -The tedious, error-prone task of keeping track of HTML nodes that can/could/would/should have focus is something that Ember could do for you. + this.router.on('routeDidChange', () => { + // we need to put this inside of something async so we can make sure it really happens **after everything else** + schedule('afterRender', this, function() { + document.body.querySelector('#ember-a11y-refocus-nav-message').focus(); + }); + }) + } +}); +``` -A comparable from another JS Ecosystem: [Reach Router](https://reach.tech/router) is available for React apps. +The addon style (popular sr-only technique): -## How we teach this +```css +#ember-a11y-refocus-nav-message { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} +``` + +The addon would attempt to provide a sensible resolution for all involved: +- the performance gains from `pushState` remain in place +- users with assistive technology are informed that a page transition has occurred +- the focus is reset to the message itself, which also resets the focus of the page (as is desired for the screen-reader user) +- `aria-live` can remain available for things that genuinely need it (a page transition should not need it) + +Some [experimentation of this approach can be seen online](https://navigator-message-test-app.netlify.com/). In this example, the message _is_ shown for demonstration purposes, but in the final product, the message would not be shown. + +What do users with screen-readers think? "It works better than I thought it might." + +### Solution #2 - Content Focus + +What about content-level focus? [Ember-self-focused](https://github.com/linkedin/self-focused/tree/master/packages/ember-self-focused) and [ember-a11y](https://github.com/ember-a11y/ember-a11y) are both examples of community-produced addons that attempt to provide a contextual focus, based on new page content. + +After some research, we discovered that we could provide content-level focus by using (currently private) API to place focus around the content of the application. -For phase one, the guides should include information about skip links, since the application author will need to add those in themselves (just as they would for a static site). For phase two, we would explain that skip links are no longer needed because we have integrated that functionality in a more machine intelligent way. +For the template, we could take one of two approaches: -### Skip Links -A skip link is an internal link at the beginning of a hypertext document that permits users to skip navigational material and quickly access the document's main content. Skip links are particularly useful for users who access a document with screen readers and users who rely on keyboards. +1. wrap the `{{outlet}}` on application.hbs with an element with the tabindex and id attributes, like this: -Directly inside the `body` element, place a skip link like this: +```hbs +
+ {{outlet}} +
+``` - Skip to main content +2. change the application.hbs outlet so it is different from the usual `{{outlet}}`, and have it already include what we need: -This allows the user with assistive technology to skip directly to the main content and is useful for not needing to repeatedly navigate through a navbar, for example. +```hbs +{{application-outlet}} +``` +In this way, if an application author did not want to use the `{{application-outlet}}`, for whatever reason (maybe they are nesting apps, or maybe they already have a focus solution) they could remove the `application-` and be left with a classic outlet. -It's possible that this solution is a [spherical cow](https://en.wikipedia.org/wiki/Spherical_cow), and the complexity of route transitions in a framework deserves an accessibility solution that is is equally complex. +Either way, we'd depend on the private API `renderSettled` to allow us to focus on the application outlet after the route has changed (a little hand-wavy since it's just an idea): + +```js +export default Route.extend({ + router: service('router'), + init() { + this._super(...arguments); + this.router.on('routeDidChange', transition => { + + if (transition.to !== null) { + emberRequire.renderSettled().then(function() { + document.body.querySelector('#ember-primary-application-outlet').focus(); + }); + } + }); + } +}); +``` + + +## How we teach this It's possible that enterprise Ember users have already implemented their own solution, since governments in many countries require this by law. An out of the box solution would need to be well-advertised and documented to avoid any potential conflicts for users. + ## Alternatives 1. We could try to implement something like Apple’s first responder pattern: https://developer.apple.com/documentation/uikit/uiresponder/1621113-becomefirstresponder 2. Wait for a SPA solution to be implemented in assistive technology. I would note that this option seems less than ideal, since this specific issue has been reported to the different assistive technologies available, and has been an on-going issue for many years now. ## Unresolved Questions - how would we handle loading states? -- how does/could/would this fit in with the router helpers work that Chad did? ## Appendix -### Related RFCs -- [Route Helpers](https://emberjs.github.io/rfcs/0391-router-helpers.html) by Chad Hietala ### Related Reading - Accessibility APIs From 19d142a1c4c5358f7e2ec57de290ac0c93dbfdf0 Mon Sep 17 00:00:00 2001 From: Melanie Sumner Date: Mon, 13 May 2019 13:45:29 -0500 Subject: [PATCH 05/11] Update 0000-a11y-routing.md updated original date --- text/0000-a11y-routing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-a11y-routing.md b/text/0000-a11y-routing.md index cdb8230302..2c6ed4bbe2 100644 --- a/text/0000-a11y-routing.md +++ b/text/0000-a11y-routing.md @@ -1,4 +1,4 @@ -- Start Date: (YYYY-MM-DD, updated on 2019-05-13) +- Start Date: (2019-01-18, updated on 2019-05-13) - RFC PR: (leave this empty) - Ember Issue: (leave this empty) From 467e2f2346b25a62e6403649da40f6ca7fb8711a Mon Sep 17 00:00:00 2001 From: Melanie Sumner Date: Mon, 13 May 2019 13:47:31 -0500 Subject: [PATCH 06/11] Update 0000-a11y-routing.md Update navigation message --- text/0000-a11y-routing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-a11y-routing.md b/text/0000-a11y-routing.md index 2c6ed4bbe2..264e5ea656 100644 --- a/text/0000-a11y-routing.md +++ b/text/0000-a11y-routing.md @@ -37,7 +37,7 @@ The component template: ```hbs ``` From 381e66743c966109a3874bdc2c7f6f029fa10d2c Mon Sep 17 00:00:00 2001 From: Melanie Sumner Date: Tue, 14 May 2019 10:29:53 -0500 Subject: [PATCH 07/11] Update 0000-a11y-routing.md typo fix --- text/0000-a11y-routing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-a11y-routing.md b/text/0000-a11y-routing.md index 264e5ea656..c6e71c16ff 100644 --- a/text/0000-a11y-routing.md +++ b/text/0000-a11y-routing.md @@ -36,7 +36,7 @@ One potential solution is that we don't overwhelm the screen reader user with th The component template: ```hbs -