UI Development



AJAX are a common part of modern web design. They speed up the user experience by bypassing the need for full page reloads. You can load small parts of user interface or content at a time, either at user’s request or based upon other events. The biggest two issues with AJAX are:

  • knowing when and how to notify screen reader about new content
  • focus management

This blog addresses the accessibility issues related to the following four implementations of AJAX –

  • Lazy Loading

  • Infinite scrolling

  • Interstitial Views

  • Single Page Applications

Lazy Loading

“Lazy Loading” AJAX content is the content that loads almost at the same time as the rest of the page, but there is a slight delay, due to things like server-side script delays, complex database queries or anything that might slow down the web page. Lazy loading is a great design approach with almost no impact on accessibility. Problems can arise though, under two circumstances: if the user arises at the blank spot where the content has not loaded yet when developers overwhelm the user with their “ARIA live” messages saying “content loaded”.

Placeholders for AJAX should inform screen readers that content is loading.
If user arrives where content has not yet loaded, it’s best to have a placeholder there.
Good Example: For sighted users, the placeholder can contain a spinner or progress bar. One of the easiest ways to let screen readers know AJAX content is loading is to simply use an icon and give it alt text that says “please wait” or content is loading.

“Lazy loading” AJAX content should not be announced as it loads.
If there is a reason to  believe that content will load slowly then ARIA live announcements may be appropriate, bit if the content is designed to load quickly, it’s best to make no announcements at all. Hearing all the announcements for quickly loading content can become overwhelming.
Good Example: In a good design there are no ARIA live announcements when small pieces of content are loaded. The content should load quickly enough that it would be highly unusual for a user to arrive at those areas. If they do happen to arrive before the content is loaded, there is a placeholder image with alt text that says, “loading content”.

Infinite scrolling

An “infinite scrolling” feature MUST allow users to reach all areas of the page with the keyboard.
An “infinite scrolling” feature is an area of the page that continues to load new content every time the user scrolls or tabs near the bottom of the page. It was invented as a way to allow users to access continuous content — like Facebook feeds — without having to do anything but scroll. The idea succeeds well enough for mouse users, but it makes it very difficult for keyboard users, who can never tab past the infinite scrolling area because every time they try to do so, new content loads and inserts itself next in the tab order.
Bad Example: Infinite Scrolling with no way to reach the right column with the keyboard
As users tab through the infinite scrolling area, they will never reach the bottom, which means that they can never reach the content on the right by progressing forward through the content. They might be able to reach it in other ways, such as navigating backward through the page, but it will still be a frustrating and confusing experience. Users who are blind may not even realize there is any content after the infinite scroll area.

Good Example: The infinite scrolling area is the last feature on the page
In this example, there is no content after the infinite scrolling area, so there is no worry about users not being able to reach content past that area. The infinite scrolling may still be a bit confusing for some users, but overall this is an acceptable approach.

An “infinite scrolling” feature MAY be activated only at the user’s request.
One way to fix the accessibility problems of infinite scrolling areas is to abandon the automated aspects of it, and let users choose when to load new content.
Good Example: Additional (“infinite”) content triggered only by user request
This example is the most accessible to the widest range of users. Instead of scrolling automatically upon reaching a certain point, users are presented with a button that gives them the option to load more content if they want to. With this approach, it can even be acceptable to put content after the middle section, because users will always be able to get to all areas of the page without any problem.

Interstitial Views

Screen reader users MUST be informed when a web page launches an interstitial view, a progress indicator, or enters into a paused or busy state. The worst thing that screen reader users can hear after activating a button is silence.
If screen reader users experience silence, they’re likely to ask themselves, “Did it work?” “Did my screen reader freeze up?” “Did my browser freeze up?” “Should I refresh the page?” They need some feedback.
When the user requests an action that will take a long time to complete, such as searching for airline tickets, it is common to present an interstitial or intermediate page to users as a way to confirm that the link worked, and that the results are coming if they wait. Screen reader users need that same kind of confirmation. The following two options are usually the most appropriate:

  • Move the focus to the interstitial message OR
  • Announce the interstitial message via an ARIA live region

Ideally, the interstitial message will not be on the screen long, so make it a short message to ensure the screen reader can read the whole thing before the next page appears.

Good Example: Interstitial view
Under correct implementation, when the user activates the button to launch the interstitial view, the original view disappears and is replaced by the interstitial view. The screen reader reads the content of that view because it is in an aria-live region. When the interstitial view goes away, the original page returns, with the addition of the search results table. After a brief intentional pause, the focus moves to the table, and the screen reader reads the caption of the table.

There are two key points to keep in mind while making accessible interstitial views
1. When using ARIA live regions, be sure that the region is in the DOM on page load and that it is empty to begin with. Insert the message into the live region when appropriate.
2. When moving focus to a container that has been loaded via AJAX, be sure to use a JavaScript timeout (generally 1 to 2 seconds) AFTER the content has loaded before sending the focus to the new container. Also, be sure that the container is set to tabindex=”-1″.

Single page applications 

Single page applications were invented to speed up web sites. Rather than force a complete page refresh every time a person clicks on a link, single page applications typically load just the main content, plus a few variables, such as the page title, page-specific styles (if any), and page-specific JavaScript (if any). The two main accessibility problems that need to be solved are:

  1. Notifying users when new content is loaded (silence is bad). Whenever screen reader users activate links, buttons, or controls, they need to hear some feedback. The design ought to have a standard method for handling AJAX-driven links and form submissions.
  2. Managing keyboard focus. Oftentimes users click on links in DOM nodes that will be gone after the new content loads (e.g. when they click on a link in a paragraph in the main content area; that paragraph will be completely gone after the main content is overwritten with the new content). Almost always this causes the focus to be lost completely, going back up to the top of the page. That may be partially OK in some circumstances (when going to the top of the page is desirable), but it’s best to always plan for the focus to go to a specific location whenever there is a risk that the focus will be lost.

Screen reader users SHOULD be made aware when new “pages” are loaded in single-page applications.

There are two main ways to notify the user when new AJAX content has loaded:

  1. Move the focus to the new content
  2. Announce the new content via aria-live

Either of these can be appropriate, depending on the circumstances, but in most simple single-page applications, it usually makes the most sense to move the focus to the new content.

Moving the focus to the new content –

Keep the following in mind when sending the focus:

  • The container MUST be set to tabindex=”-1″. In order to receive the focus successfully, the container where the focus is being sent must be set to tabindex=”-1″.
  • The focus MUST be temporarily moved, then sent to the correct destination. If the focus is already on the element where the focus will be sent, some screen readers (VoiceOver on macOS in particular) will not read the text in the element, even if the text in the element has changed. One of the easiest ways to fix this is to temporarily send the focus to an empty container, then send it to the desired destination. Not all screen readers require the temporary focus trick, but it is a requirement (at least for now) if you plan to support VoiceOver and Safari.
  • There MUST be a delay before sending the focus to the final destination. VoiceOver on iOS is particularly prone to losing the focus on AJAX content if the focus is sent too soon. A good delay duration is typically about 1 second, but you should test your script to ensure the delay you have chosen is sufficient. Most screen readers can tolerate much shorter delays, but iOS cannot (unless/until they fix it).
  • The focus SHOULD be sent to a heading at the beginning of the AJAX content. If at all possible, send the focus to a heading at the beginning of the AJAX content. The heading will act essentially like the title of the new page. With single-page applications, the <title> won’t be read by most screen readers, so having a heading that acts as a sort of substitute for the  <title> is a good approach. The first <h1> heading in the AJAX content can be selected in JavaScript: $(“#container h1:first”). It may also be acceptable to send the focus to the container of the new AJAX content — such as  $(“#content”)— if a heading is not available.

Announcing the new content via aria-live –

In less common cases, it may be appropriate to keep the focus on the button (or control or link), rather than send the focus to the new content. If that’s the case, the user still needs to hear confirmation that something happened.

  • Optional: Confirm that the action is in progress. If there is a risk that there may be a delay in receiving the AJAX response, you could announce something like “page loading,” or “searching,” or “in progress,” or something along those lines. This could apply to AJAX responses that retrieve content from third-party servers or database queries that might take a few seconds.
  • Confirm the result of the action (success or failure message). Let users know that the event has finished, and that it was either successful or a failure. You could say things like “Message sent,” or “Error: message could not be sent,” or “Additional results loaded,” etc.
  • Keep the message brief. ARIA live messages can’t be replayed, and there is a risk that the user may interrupt them, so if the message needs to be heard in its entirety, the user is more likely to listen to a short message than a long one.
  • Choose either assertive or polite. An assertive message (aria-live=”assertive”) is usually appropriate when the ARIA live message is the result of the user activating a button or link, but aria-live=”polite” is also an option.
  • Optional: provide (brief) instructions related to the new content. An example would be a custom auto-complete form field that loads options as soon as the user starts typing. The announcement could say something like, “Four options available. Use down arrow to select.”


Good Example: Link click event with single-page application accessibility techniques
There are quite a few things to keep in mind when a user clicks a link in a single-page application. To begin with, we’ll just show the basic click event and list the required elements in the comment in the code. We’ll expand on the actual code a bit later on this page.

Code –

$(document).on('click', 'a', function (e) { 
  /* Don't let links act like regular links */
  /* Run functions that do the following:
    - empty the containers that the AJAX content will fill
    - load the AJAX content
    - update the document with the AJAX content
    - update the browser history
    - send the focus temporarily to an empty container
    - send the focus to the desired destination after a delay

The browser history MUST be updated when an AJAX event is the result of clicking on a link OR if the event is such that a user would expect to be able to use the “back” button after the event.

Users expect to be able to use the back button after clicking on a link or after activating a feature that seems to load a new page. On single-page applications, the browser history will not be updated unless the page includes JavaScript code to cause the browser history to change. It’s something that has to be planned and implemented in a systematic way.

Good Example: Updating the browser history

The code below shows a simplified way of updating the browser history using history.pushState().

Code –

var newUrl = 'https://dequeuniversity.com';
var newTitle = 'Deque University';
  url: newUrl,
  title: newTitle
}, newTitle, newUrl);

The page MUST respond appropriately when the user activates back or forward functionality in the browser.

The browser sends a popstate event when the “Back” or “Forward” buttons (or keyboard commands) are activated in the browser. You can intercept that event and apply the appropriate accessibility techniques.

Good Example: A popstate function with accessibility features
This example shows where to put the accessibility features on a popstate event. The details are shown later on in the next example.

Code –

$(window).on('popstate', function (e) {
  var state = e.originalEvent.state;
  if (state !== null) {
    Run functions that do the following:
    - empty the containers that the AJAX content will fill
    - load the AJAX content
    - update the document with the AJAX content
    - send the focus temporarily to an empty container
    - send the focus to the desired destination after a delay
  else {
    (optional conditions when a popstate
    is not activated)

Putting it all together –

It’s helpful to see (or hear) these principles in action, so the following examples help us understand things in a more concrete way.

Good Example: Accessible single-page application
This is a straightforward way to create a single-page application without relying on JavaScript frameworks. The code below does use jQuery, but the selectors could easily be modified for plain JavaScript.

Note that in most browsers a dotted line (or similar) shows on the screen when the focus lands on the heading in the main content. On a real web site, you might want to suppress that particular focus indicator (but not the focus indicators on links, buttons, etc.!), but we left it intact here to make it more obvious when and where the focus lands.

Turn on a screen reader to experience this example in action.

The way this particular example is set up, all of the pages are real HTML files, and the links still work even when JavaScript is turned off. If you don’t want real files, you could use web server redirects to achieve the same end result. Every page references the single-page application script, so any page can be the starting point for the one-page experience.

Some features that are not yet in this example, but which could be added include:

  • Handling links that go to external sites or non-HTML documents (.pdf, .docx, .mp4, etc.)
  • Handling form submissions, including validation error messages and success messages

The JavaScript code is shown below:

Code –

$(function () {
  var currentContent = $('#innerContent');
  var currentHeading = $('#contentHeading');
  var newTitle;
  var newHeading;
  var newContent;
  var load = function (newUrl) {
    /* get AJAX content */
    $.get(newUrl).done(function (data) {
    /* Parse the page into variables to insert separately */
    newTitle = $(data).filter('title').html();
    var newMain = $(data).filter('main').html();
    newHeading = $(newMain).filter('h1').html();
    newContent = $(newMain).filter('#innerContent').html();
    /* update document with AJAX content variables */
    document.title = newTitle;
  function focusH1(){
    /* Set focus temporarily on an empty div,
               to force screen readers (especially VoiceOver)
               to read the focused item if the focus was already
               on that same item before we re-sent the focus to it */
    /* Set focus on the h1 at the beginning of the content
    after a brief delay (the delay is mostly for iOS;
    sometimes shorter delays work, but they are risky
    in iOS, so a minimum of 1 second is recommended) */
    }, 1000);
  function emptyOld() {
    /* empty the original content
    that is about to be replaced
    so that iOS doesn't read old information */
    $('#contentHeading, #innerContent').empty();
  $(document).on('click', 'a', function (e) {
    /* activate the functions when a link is clicked */
    /* don't let links act like regular links */
    newUrl = $(this).attr("href");
    /* update the browser history */
      url: newUrl,
      title: newTitle
    }, newTitle, newUrl);
  $(window).on('popstate', function (e) {
    var state = e.originalEvent.state;
    if (state !== null) {
      /* activate the functions if a popstate is detected,
      e.g. if "back" or "forward" are used in the browser */
      document.title = state.title;
    else {
      /* if the page loads normally
      (not by clicking on a link or by
      using the back/forward buttons) */


Bad Example: Single-page application without accessibility features
In a bad example when the user activates a link, the screen reader says nothing. Screen reader users may wonder if something is broken.


There’s enough proof that Internet is not accessible enough. People with disabilities(including 7 million blind people of USA) have been continuously fighting for accessible and more inclusive web from last 20 years. While developing websites – it is the responsibility of developer community to make sure that THE INTERNET IS FOR EVERYONE!


Hope this blog turns out to be useful for all the readers out there!


About The Author