The a11y Monthly: Accessibility and JavaScript

At Yoast, I help improving Yoast's technology accessibility, spreading accessibility culture and blogging about it. I try to do my best to do the same for WordPress, where I'm contributing as a permanent core committer.

In the last couple years, we’ve seen an increased adoption of JavaScript libraries and frameworks to build web applications. At Yoast, some important parts of our applications, such as our Yoast SEO plugin, have already moved to JavaScript. In the future, we’ll build most of our interfaces using React components. This shift to JavaScript is a general trend in today’s web development. In this post, I’ll try to describe how this shift relates to Web Content Accessibility. Besides that, I’ll recommend a couple of best practices.

Is it good or bad?

People often ask me if JavaScript is good or bad for accessibility. The short answer is: it depends. By their nature, web technologies and programming languages aren’t good or bad for accessibility. It’s not the technology; it’s the way developers use a technology that can impact accessibility. JavaScript is not any different.[product_banner product=”seo”]

With great power comes great responsibility

Today, developers use JavaScript libraries and frameworks to generate the actual “web pages” or sections of web pages. At the same time, they use it to implement the application logic. It’s a great power that developers should handle with care.

Web content accessibility is about the quality of the markup, where the HTML is king. The document structure must convey information in a logical and straightforward way, plus the markup should be semantic. It should provide all the information users of assistive technologies need to get a solid grasp of the content and purpose of a document. It should give all the available actions, plus provide the necessary feedback.

Technology doesn’t matter

Whether the markup gets output by JavaScript or a different technology doesn’t matter. Even when using JavaScript to produce and output HTML views, all the basic accessibility principles still apply. Developers need to be aware that some JavaScript libraries and frameworks tend to impose their HTML patterns.

React, for example, requires all your components to be wrapped in a main HTML container element. I’ve seen React components use a div element for this purpose, and this makes sense in most of the cases. But it’s not always the best choice. Depending on the component’s purpose, a more semantic wrapper might be the best option. As developers, we’ve been talking about web standards and avoiding non-semantic HTML for years; we shouldn’t forget this in a JavaScript world.

In the same way, developers should build React user interface controls using semantic HTML. I’ve seen components using wrong elements and events, though. Buttons that are not real buttons but span elements, DOM events that don’t work when using a keyboard, and so on. This is not a limitation of React, though, it’s an implementation that can be improved.

Developers can produce poorly structured and non-semantic HTML using any language. Again, developers must structure their application’s markup using semantic elements. Also, they must avoid introducing accessibility barriers.

A couple of recommended best practices

I’m not pretending to cover all the potential issues related to JavaScript and accessibility in this post. However, I’d like to focus on a couple of things related to screen readers developers should be aware of: re-rendering and DOM events.

Avoid continuous re-rendering

JavaScript libraries and frameworks that generate front-end views can do heavy DOM manipulations in an optimized and efficient way. They’re used to build single-page applications or sections of an application that frequently update based on users interaction or a change of their data model.

Many modern JavaScript-based applications are built this way. These applications update portions of a web page without a page reload. To do this, they need to manipulate and update the DOM. For users of keyboards and screen readers, the re-rendering can introduce a potential accessibility barrier.

When using a keyboard or a screen reader, navigating through content is a linearized process. With a keyboard, users can tab through focusable elements in the order they’re in the markup. Screen readers offer several shortcuts to jump to different sections of a web page, but navigation is still linearized. So, when a portion of the DOM gets re-rendered, developers should take special care to handle the browser and screen reader “focus”. They’re two slightly different concepts (not entering into technical details now). Developers need to know that there’s a high chance of a focus loss.[product_banner product=”site-structure-training”]

Loss of focus

When a portion of a web page gets re-rendered, it often gets completely removed from the DOM and then injected again with a new state and data. Visually, this happens so fast that is hardly noticeable. By the way, when the keyboard focus or the screen reader “focus” is within the re-rendered region, then in the exact moment that part of the DOM gets removed, the focused element gets removed too. Browsers won’t know where to put focus anymore. So, keyboard and screen reader users will lose the context. They will be forced to start navigating content from scratch or a place in the user interface browsers will “think” is the right place to put focus on.

Sometimes this kind of focus loss is so severe that it makes an application impossible to use with a keyboard or a screen reader. There are ways to mitigate this issue. For one, you should always test your application using a keyboard. When a DOM update is necessary, then focus should be programmatically handled in a logical way, moving or restoring focus in a proper place.

Some JavaScript libraries, React for example, tend to perform a re-rendering of the components even when it’s not strictly necessary. Developers should be aware of this, and try to avoid continuous, unnecessary, re-renderings. From my understanding, React offers some ways to control the re-rendering to some extend. For instance, you should responsibly use shouldComponentUpdate(). It’s a great opportunity to optimize your component. Make sure that you’re not re-rendering if you don’t need to. But, from an accessibility perspective, this is an area where JavaScript libraries and frameworks should improve. They should offer a more strict control over the re-rendering.

DOM events may not work as you expect

When using a screen reader, DOM events, especially keyboard events, may not work as you expect. To understand why this happens, developers should be aware of screen reader interaction modes. They should pay extra attention to Windows screen readers. Understanding what virtual/browse mode and forms/focus mode do help to avoid common mistakes that make content inaccessible to screen readers. VoiceOver on macOS has a different interaction model compared to Windows screen readers. What developers need to know is that all screen readers intercept keypresses before they reach the browser. However, there are a few exceptions like the tab key or when the enter key is used to activate a link.

When in virtual/browse mode, screen readers use keypresses for their own purposes, to offer users several commands and shortcuts to navigate content. Screen readers switch to forms/focus mode when necessary, for example within a form, to pass back keypresses to the browser.

Operating forms

When operating within a form, users need keypresses to type in the form fields. They also need it to move the cursor inside the typed text, select the typed text, activate other form controls, and so on. Instead, when in virtual/browse mode, the browser is unaware a keyboard event occurred. There’s no event fired at all. This switch mechanism works pretty well with native HTML elements because screen readers know what to expect from these elements.[product_banner product=”seo”]

However, when it comes to custom/rich internet widgets, there’s a chance to introduce accessibility barriers. In this context, a custom widget is anything that is not “native” HTML. This could be a user interface component with an interaction model that is not standard and can’t be predicted by the markup.

Say we’ve built a user interface component that relies on some keydown events attached to items in a list. It could be a menubar or a tab set. Since list items are not natively focusable and don’t natively support keyboard interaction, screen readers will stay in virtual/browse mode. They have no reason to switch to forms/focus mode. So all our keydown events won’t fire at all. This can be surprising at first. Everything functions nicely in a browser, but when a screen reader is running, everything stops working.

The solution is to let screen readers know they have to switch interaction mode. Pass keypresses back to the browser so that they can fulfill their original purpose. As a best practice, you should always prefer elements that have native support for keyboard interaction to other elements. Screen readers know what a button is and they’ll behave correctly. When using other elements, then a proper use of ARIA roles is the solution. Certain ARIA roles, when applied to custom widgets, inform screen readers that HTML elements have a specific purpose. Besides that, they’ll inform that virtual/browse mode is not appropriate. The result is that screen readers will pass back keypresses to the browser as required by the specified ARIA role, thus making our custom widget work properly.

Exploring new territories

It’s worth mentioning that this particular area of accessibility is still uncharted territory. We’re in the midst of a technology shift towards JavaScript-based user interfaces. New tools and implementations arrive every day. Accessibility best practices in this area need to be consolidated and improved. At Yoast, we’re passionate about web content accessibility. This offers us a fascinating opportunity to explore new challenges and solutions.

Want to help?

Accessibility is a process based on continuous improvements, testing, iteration, and development. You can help. We’re always open to feedback and contributions. Please do not hesitate to let us hear your voice. Do report any issues or potential improvements you notice in our products.