Skip to Main Content
Technical & Programming07 July 2026

Accessibility in Single Page Applications (SPA: React & Vue)

Author

Redaksi Disabilitas.com

4 Min Read2 Views

Accessibility in Single Page Applications (SPA: React & Vue)

Single Page Application (SPA) architectures like React, Vue, and Angular have revolutionized web speed. Instead of reloading the entire HTML page from the server every time a link is clicked, SPAs simply swap DOM components asynchronously in the background.

However, this speed birthed a highly fatal accessibility problem: When the page does not refresh, the Screen Reader does not know that the route has changed. Adapting analysis from Practical Web Accessibility (Ashley Firth), this article will dissect how to patch the biggest accessibility gap in the modern SPA ecosystem.


1. The Silent Route Syndrome

In a classic Multi-Page Application (MPA) architecture (like WordPress), clicking an "About Us" link will fully reload the page. The browser will move the cursor focus to the very top of the new page, and the Screen Reader will automatically announce the document's <title> tag: "About Us - Company X".

In an SPA architecture, clicking "About Us" merely replaces the contents of the <main> element. What happens to a blind user? They click the link, the content on the screen changes instantly, but the Screen Reader remains completely silent. Their cursor focus is still left behind on the old navigation link. The user will assume the button is broken or the website has frozen.


2. Restoring the Focus Lifecycle (Focus Management)

To fix this, you must manually replicate the classic browser behavior using JavaScript within your routing library (such as react-router-dom or vue-router).

Every time the route changes (the URL changes), you must:

  1. Intercept the route change event.
  2. Explicitly move the cursor focus using element.focus().

Where should the focus be moved? The industry best practice is to move the focus to the <main> wrapper element or directly to the new page's <h1> tag.

Since an <h1> tag usually cannot receive focus (it's not an interactive element), you must add tabindex="-1" to the element so it can receive focus programmatically via JavaScript (but remains inaccessible via the Tab key).

// React Example: Shifting focus when the route changes
import { useEffect, useRef } from 'react';
import { useLocation } from 'react-router-dom';

const PageWrapper = ({ children, title }) => {
  const headingRef = useRef(null);
  const location = useLocation();

  useEffect(() => {
    // Move focus whenever the path changes
    if (headingRef.current) {
      headingRef.current.focus();
    }
  }, [location.pathname]);

  return (
    <main>
      <h1 ref={headingRef} tabIndex="-1">{title}</h1>
      {children}
    </main>
  );
};

With the code above, every time the user navigates pages, the Screen Reader will immediately announce "About Us, Heading level 1", notifying the user that they have successfully moved locations.


3. Handling Asynchronous Content Updates (Live Regions)

Aside from page navigation, SPAs frequently update small parts of the interface in real-time. For example:

  • A "Data successfully saved" toast message appears.
  • The notification number on a bell icon changes from 3 to 4.
  • Search results populating below a search bar as the user types.

For these asynchronous events, you must use the aria-live attribute. This attribute listens to DOM changes within that element and immediately announces them to the Screen Reader.

  • aria-live="polite": The Screen Reader will wait until the user finishes listening to the current sentence, then announce the update. Use this for the vast majority of non-critical notifications (like search results).
  • aria-live="assertive": The Screen Reader will forcefully interrupt whatever it is currently reading and immediately announce the update. ONLY use this for emergency warnings (like "Your bank session will expire in 10 seconds").

4. Announcer Components

Many modern SPA frameworks have built-in Announcer plugins or third-party libraries (like react-a11y-announcer).

This is a visually hidden <div aria-live="polite"> element tucked in the corner of the DOM, specifically tasked with swallowing text strings from various app components and announcing them to the Screen Reader. This library significantly reduces the complexity of managing aria-live across every single atomic component.


5. Conclusion

Building a technically fast Single Page Application is incredibly easy with modern tools. However, building an inclusive SPA requires mature orchestration. In an SPA, the Front-End Developer is fully responsible for cursor focus management—a task previously handled for free by the browser. By mastering the tabindex="-1" and aria-live techniques, you ensure that the speed of your application never leaves anyone behind in silence.


References

The analysis regarding client-side routing vulnerabilities (SPAs), DOM cursor focus management, and live region implementation techniques are extracted from the dynamic application development guidelines within Practical Web Accessibility by Ashley Firth. The React integration techniques above refer to modern software engineering industry standards for WCAG operational compliance.

What do you think?

Give your reaction to this article