0

What to do so that screen reader reads document loading while loading the document and document loaded when document component gets loaded in react?

Sarfraj Ansari
  • 119
  • 2
  • 11

2 Answers2

1

You will need to see how to do the following in React, but the principles for AJAX page loading carry across to all SPAs.

The only difference between this and what you asked for is you don't announce "document loaded" instead you focus the <h1> on the page as that is more useful to screen reader users.

Before Loading

You need to signal to a user that a page is loading if you are using a SPA pattern (and therefore interrupting normal navigation).

e.g. I click your link you need to let me know that an action is being performed (loading.....) as you intercept the normal browser behaviour with e.preventDefault() or equivalent.

The simplest way is to use aria-live=assertive on a region that explains the page is loading.

You may want to visually hide this aria-live region (so that only screen readers can access it) so I have included a class to do this in the snippet below, however for the demo I have left the region visible. Here is a link to my original discussion on why to use this class to hide content.

After Loading

Additionally when the new page loads you need to manage focus.

The best way to do this is to add a level 1 heading (<h1>) to each page that has tabindex="-1".

Once the page loads the last action you perform in your JavaScript navigation function is to place the focus onto this heading and then clear the aria-live region

This has two benefits:

  • it lets the user know where they are now
  • it also lets them know when the page load is complete (as AJAX navigation doesn't announce when the page is loaded in most screen readers).

By using tabindex="-1" it means that the heading won't be focusable by anything other than your JavaScript so won't interfere with the normal document flow.

Example

var link = document.querySelector('a');
var liveRegion = document.querySelector('p');
var originalPage = document.querySelector('.currentPage');
var newPage = document.querySelector('.newPage');

link.addEventListener('click', function(e){
    e.preventDefault();
    liveRegion.innerHTML = "loading";
    simulateLoading();
});

//this function simulates loading a new page
function simulateLoading(){
    window.setTimeout(function(){
    //this bit just hides the old page and shows the new page to simulate a page load
    originalPage.style.display = "none";
    newPage.style.display = "block";
    
    
    //////////////ACTUAL CODE/////////////////
    
        // grab the heading on the new page (after the new page has loaded fully)
        var mainHeading = document.querySelector('.newPage h1');
        //focus the main heading
        mainHeading.focus();
        // reset the aria-live region ready for further navigation        
        liveRegion.innerHTML = "";
    }, 1000);
}
.newPage{
   display:none;
}
<div class="currentPage">
<h1>Current Page</h1>
<a href="#">Click me to navigate</a>
<p class="live-region visually-hidden" aria-live="assertive"></p>
</div>


<div class="newPage">
<h1 tabindex="-1">New Page Heading Focused Once Loaded</h1>
<button>Just a button for something to focus so you can see you can't refocus the H1 using the Tab key (or shift + Tab)</button>
<p>Below is the visually hidden class I mentioned, this can be used to hide the aria-live region visually if you wish, I have left it visible for demonstration purposes.</p>
<pre>
.visually-hidden { 
    border: 0;
    padding: 0;
    margin: 0;
    position: absolute !important;
    height: 1px; 
    width: 1px;
    overflow: hidden;
    clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
    clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
    clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
    white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}

</pre>


</div>
GrahamTheDev
  • 22,724
  • 2
  • 32
  • 64
1

Adding aria-busy="true" and aria-hidden="true" attributes to the component while loading will hide the content from screen readers temporarily.

For the announcement, somewhere else, create a <div role="status"> and add/remove child elements to it that will be announced when loading/loaded.

End result:

<main>
  <Document
    aria-busy={isLoading ? 'true' : null}
    aria-hidden={isLoading ? 'true' : null}>
  </Document>
  <div role="status" class="visually-hidden">
    {isLoading ? (
      <span key="loading">
        Document loading
      </span>
    ) : (
      <span key="loaded">
        Document loaded
      </span>
    )}
  </div>
</main>

The key props are there to make sure React doesn't reuse the same <span> element.

The .visually-hidden class makes it invisible except to screen readers:

.visually-hidden {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}