Making Ember Applications' UI Transitions Screen Reader Friendly
October 17, 2018
Note: An image of the LinkedIn-created logo for the self-focused open source project accompanies this post.
At LinkedIn, we strive to make our user interfaces (UIs) accessible for all of our users, including people with disabilities. So when the modernized, Single Page Application (SPA) version for linkedin.com was being developed a few years ago, we wanted to ensure that it was compatible with screen reading software such as JAWS (Job Access with Speech) and VoiceOver.
For context, 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).
Initially we wanted to use the existing ember-a11y addon to make the dynamic UI transitions screen reading software compatible. However after initial integration, we discovered that this addon could not be used as is and needed a couple of prerequisites to be addressed. Since the launch date of the modernized, Single Page Application (SPA) version for linkedin.com was set and could not be postponed, we worked on an internal solution, which not only addresses the use cases for linkedin.com, but also improves developer ergonomics for handling nested route transitions.
For background, 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.
However, keeping track of the HTML nodes to be focused can be tedious, repetitive, and error-prone since there could be hundreds of nodes that need to be programmatically focused in a SPA, thus we created the ember-self-focused addon. It makes an Ember application’s UI transitions screen reading software friendly, and we are pleased to open source it today.
Architecture and design
The self-focused component can be consumed like so:
A self-focused component on didInsertElement and on didRender, passes its corresponding HTML node as an argument to focus-manager, by invoking the didInsertElement and didRenderElement methods of the focus-manager, respectively.
For a render cycle, only one HTML node can and will be focused; focus-manager decides which HTML node will be focused for a render cycle.
If there is only one self-focused component for a render cycle, focus-manager focuses the HTML node corresponding to the self-focused component. However, things become tricky if there are nested self-focused components, for example in nested routes, for a render cycle:
The focus-manager relies on the following principles to identify the HTML node that needs to be focused:
The didInsertElement life cycle hook gets executed
For the initial render, the HTML node corresponding to the top-most self-focused component in the DOM will be focused, since the UI has been updated from that HTML node.
For a re-render scenario, the HTML node corresponding to the child most self-focused represents the HTML node from which the UI has been updated for the current render cycle.
Collaborate with us
Over time, this solution has been adopted in other LinkedIn products beyond our flagship app, such as LinkedIn Learning and LinkedIn Learning Enterprise, allowing their UI transitions to be accessible and usable for users of screen reading software.
Do you have a use case that should be addressed by ember-self-focused, or have you found an issue with it or a way to improve it? Feel free to raise a pull request or an issue against github.com/linkedin/self-focused. We look forward to your contribution. Please refer to the CONTRIBUTING.md at github.com/linkedin/self-focused for more information.