Single page app recommendations - trussworks/accessibility GitHub Wiki

A single page application is a web application that does not require a page refresh when loading different elements onto a page. This type of application has gained more popularity with the advent of different Javascript frameworks such as React, Angular, or Vue to name a few.

Keep in mind that SPAs will take more work to be accessible by virtue of not taking advantage of built-in browser functionality that assistive technologies can interact with. There are some promising developments in that department in the future (like a page being able to tell the browser that it is displaying new content and should treat things as if there is a new page loaded), but it's going to be a while before the results of those efforts are seen in the wild.

Oftentimes single page applications are chosen in order to mimic the functionality of native applications. It is important to remember to continue to use HTML 5 semantic markup order to ensure different areas of the DOM are identified correctly.

Updating page titles

Going from one page to another in a SPA does not require a page refresh. It’s important that each page of a web application has a unique title to properly identify its content for screen reader users. This means that the title of the page may not change by default. It's important to update the title via Javascript.

The following is an example of how to set a page title using React Hooks.

import React, { useEffect} from "react";

export default function App() {

   useEffect(() => {
     document.title = "About Page";  
   }, []);

   return (
     <div className="App">
       <h1>Hello React App</h1>
     </div>
   );
}

Navigation of pages

When navigating a single page application, page changes do not lead to reloading by the browser. For those using a screen reader the page change will not be announced by default.

A good solution is to move the focus to the first page heading <h1> of the current page.

Note: by default an <h1> is not focusable. To make an element focusable via Javascript add a tabindex=”-1” attribute to it.

class FocusableHeader extends React.Component {
  headingRef = React.createRef()

  componentDidMount() {
    this.headingRef.current.focus()
  }

  render() {
    return (
        <h1
          ref={this.headingRef}
          className="focusableHeader"
          tabIndex="-1" >
          I'm a focusable header!
        </h1>
    )
  }
}

export default FocusableHeader

If you have a visible navigation that shows the active page, make sure to change styling to indicate active page to sighted users and also add aria-current=”page” for users using screen readers.

Notification of in-page changes

If information is dynamically changing on the page such as confirmation messages, progress indicators, error messages, etc. it's necessary to make sure that screen readers will announce these updates. There are several ways to accomplish this:

  • Move focus to the newly updated page content
  • Use an ARIA attributes to indicate change, such as ARIA status or ARIA live

Creating a new element in the DOM

You can trigger an alert by inserting a new element in the DOM via javascript

<span role="alert"> This is an alert message. </span>

Add a role alert on an existing element

Triggering an alert can be done by adding role=”alert” dynamically to an existing element via Javascript’

document.getElementById('alert').setAttribute("role", "alert");

Aria-live attribute

Adding an aria-live attribute to an existing element allows the screen reader to announce any content that has changed within that element.

There are three possible values:

  • Off: no vocalization
  • Polite: the vocalization will take place when the screen reader has finished the current task
  • Assertive: the screen reader interrupts the current task to inform the user It is recommended that aria-live is set on the element as soon as the page loads to maximize compatibility with different screen readers and browsers.

<span aria-live="polite">5 selected items</span>

Managing browser history

As mentioned before, single page applications do not keep track of states of your application the same way as multi-page apps where every state is usually bound to a different URL. Because of this, using the previous and next buttons in your browser may not result in expected behavior.

In order to provide the same browser history functionality as a multi-page application you will need to familiarize yourself with how your specific routing tool tracks browser history and passes data between routes. Most client side routing libraries, such as react-router, interact with the HTML5 History API behind the scenes. It’s important to understand your routing approach well because this ensures the user will have a more reliable experience in the browser when hitting the back button or refreshing the page.

QA example script

The single page application must:

  • The page title changes when navigating from page to page
  • Properly set focus to an <h1> when navigating from page to page
  • Properly announce any dynamic page updates such as alerts, progress indicators, or error messages, etc.
  • Allow users to navigate using next and previous and this is accomplished by properly store browser history or update URLs from page to page

Projects at Truss that have built accessible single page applications

  • MC-Review
  • EASi
  • MilMove
  • DOL

Further Reading

A11y recommendation for single page applications: https://a11y-guidelines.orange.com/en/articles/single-page-app/

Page titles and A11y in Single Page Applications: https://dev.to/s_aitchison/page-titles-and-a11y-in-single-page-applications-esp-react-vue-4ok8

Handling focus on route change in React: https://www.upyoura11y.com/handling-focus/

Handling page titles in React: https://www.upyoura11y.com/page-titles/

Various ways to implement LIVE regions: https://www.scottohara.me/blog/2022/02/05/are-we-live.html

The aria-live attribute and role alert: https://a11y-guidelines.orange.com/en/articles/aria-live-alert/

Accessible routing in React: ​​https://timwright.org/blog/2019/03/23/accessible-routing-in-react/

⚠️ **GitHub.com Fallback** ⚠️