Search code examples
angularaccessibilitywai-arianvda

NVDA reading only last loaded the content of div and skipping remaining


I have an 3 different div dynamically loading based on search criteria. Each div I have added "role='alert'", "tabindex='0'" and "aria-live='polite'". I have noticed DOM was loading immediately all 3 div but NVDA speaker reading only last loaded div content. I have verified in NVDA speak viewer all 3 div content displaying here but while reading it's speaking last content.

Once I click on the button. NVDA speaks line by line all content.

I am using Angular 5. Please find the below code for more information.

HTML template:

<input type="button" value="Click Me" tabindex="0" (click)="getWatsonDetails()"/>
<div *ngIf="isHiddenData">
  <div id="main{{i}}" tabindex="-1" class="chat" *ngFor="let passage of watsonResponse;let i=index;">
    <p role="alert" aria-live="polite" aria-atomic="true" tabindex="0" style="font-size: medium; margin-bottom: 4px;">
      {{passage.value.input.text}}
    </p>
    <div role="alert" aria-live="polite" aria-atomic="true" tabindex="-1" style="margin-bottom:4px;" *ngIf="passage.who==='watson'&& passage.label==='DL codes'">
      <span class="btn-group-star" *ngFor="let item of passage.value.codesWithLinks; let pwe=index">
        <button class="btn btn-secondary wlDlBtn" tabindex="0" id="pdfbtn{{i}}{{pwe}}" *ngIf="item.type==='pdf'" type="button"
                placement="top-left" attr.aria-label="{{'Document '+item.code}}">
          <span>{{'Document - '+item.code}}</span>
        </button>

        <button class="btn btn-secondary wlDlBtn" tabindex="0" id="wblbtn{{i}}{{pwe}}" *ngIf="item.type==='weblink'" type="button"
                placement="top-left" attr.aria-label="{{'Document '+item.code}} ">
          <span role="link">{{'Web Link - '+item.code}}</span>
        </button>

        <button class="btn btn-secondary wlDlBtn" tabindex="0" id="excelbtn{{i}}{{pwe}}" *ngIf="item.type==='excel'" type="button"
                placement="top-left" attr.aria-label="{{'Document '+item.code}} ">
          <span>{{'Document - '+item.code}}</span>
        </button>
        <br>
      </span>
    </div>
    <div tabindex="-1" style="font-size: medium; margin-bottom: 4px;" *ngIf="passage.who==='watson' && passage.label==='Relevance node'">
      <p role="alert" aria-live="polite" aria-atomic="true" tabindex="0"> {{passage.value.input.text}} </p> <br />
      <div role="alert" aria-live="polite" aria-atomic="true" style="display:flex;margin-top: 1em;">
        <div id="drop_{{i}}">
          <button role="checkbox" tabindex="0" id="feedbackDropBtn_{{i}}" aria-expanded="false" aria-label="select" class="drop-toggle btn flat">
            <span aria-hidden="true">Select</span>
            <span id="feedbackDropBtn" aria-hidden="true">Select</span>
            <i class="fa fa-angle-down"></i>
          </button>
          <div id="feedback_{{i}}" class="drop-show" *ngIf="!passage.isDisabled" style="height: 85px;margin-left: 3%;overflow-y: scroll;">
            <label id="check{{cb}}" *ngFor="let option of optionsArray;let cb=index">
              <input type="checkbox" id="checkItem_{{i}}{{cb}}" attr.aria-labelledby="{{'chkbx' + cb}}" name="checkbox" tabindex="0" />
              <span id="chkbx{{cb}}">{{option.code}}</span>
            </label>
          </div>
        </div>
        <span aria-live="polite" aria-atomic="true">
          <button type="button" tabindex="0" aria-label="Submit" style="text-align: center;height: 2.2em;margin-left: 0.5em;" class="submitbtn" id="submit_{{i}}">
            <span style="color:#4E8416">Submit</span>
          </button>
        </span>
      </div>
    </div>
  </div>
</div>

TypeScript code:

watsonResponse: any = [{ "label": "text", "value": { "input": { "text": "Here are the documents or links that I found that match most closely to your search criteria. Click on a link to view the document in the Document Viewer. If the link is for a webpage, the link will be opened in a new tab in your browser." } }, "who": "watson", "time": "2021-9-3|9:39:29" }, { "label": "DL codes", "value": { "input": { "text": "AB0003,AB0037" }, "codesWithLinks": [{ "code": "AB0003", "link": "", "summary": "Cultural Competency Training 2021", "type": "pdf", "DLWL_links_int": "", "checked": false }, { "code": "AB0037", "link": "", "summary": "Cultural Competency Training 2020", "type": "pdf", "DLWL_links_int": "", "checked": false }, { "code": "None", "checked": false }] }, "who": "watson", "time": "2021-9-3|9:39:29" }, { "label": "Relevance node", "value": { "input": { "text": "Were you able to find what you were looking for?  Please help us continue to improve your experience by selecting the document in the drop-down menu that best helped you and clicking submit for a thumbs-up.  If you did not find what you were looking for, please select “None” from the drop-down and click submit for a thumbs-down to continue." } }, "who": "watson", "time": "2021-9-3|9:39:29" }]

  optionsArray: any = [{ "code": "Dot net" }, { "code": "Java" }, { "code": "Python" }];
  isHiddenData: boolean = false;

  getWatsonDetails() {
    this.isHiddenData = true;
  }

Note: In real-time data render through service and also ignore CSS. Click here to see, NVDA speak viewer displaying


Solution

  • It is most likely because you are using role=“alert” on those divs. Using that role is equivalent to using aria-live=“assertive” which may override messages queued up for the screen reader.

    Per the comment from @slugolicious, the definition of aria-live (w3.org/TR/wai-aria/#aria-live) says that "User agents or assistive technologies MAY choose to clear queued changes when an assertive change occurs." That is, multiple role="alert" or aria-live="assertive" elements might clear the message from other elements so that might be the cause of the problem.

    Per the comments, having aria-live and aria-atomic set on both the <p> tag as well as the <div> tag for each alert may be causing some content to be parsed and read off twice once the initial queuing issue is resolved. As stated in https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions#additional_live_region_attributes: "The aria-atomic=BOOLEAN is used to set whether or not the screen reader should always present the live region as a whole." Setting these attributes twice around the content of each alert may be causing some content to be queued up to be read off by the screen reader twice.

    I'd suggest wrapping the <p> and <div> tags in an outer element that has aria-live="polite" and aria-atomic="true" declared just once around the entire content of each alert.

    Something like:

    <div id="alertWrapper1" aria-live="polite" aria-atomic="true"
        <p tabindex="-1" style="font-size: medium; margin-bottom: 4px;">
          {{passage.value.input.text}}
        </p>
        <div tabindex="-1" style="margin-bottom:4px;" *ngIf="passage.who==='watson'&& passage.label==='DL codes'">
          <span class="btn-group-star" *ngFor="let item of passage.value.codesWithLinks; let pwe=index">
            <button class="btn btn-secondary wlDlBtn" tabindex="0" id="pdfbtn{{i}}{{pwe}}" *ngIf="item.type==='pdf'" type="button"
                    placement="top-left" attr.aria-label="{{'Document '+item.code}}">
              <span>{{'Document - '+item.code}}</span>
            </button>
    
            <button class="btn btn-secondary wlDlBtn" tabindex="0" id="wblbtn{{i}}{{pwe}}" *ngIf="item.type==='weblink'" type="button"
                    placement="top-left" attr.aria-label="{{'Document '+item.code}} ">
              <span role="link">{{'Web Link - '+item.code}}</span>
            </button>
    
            <button class="btn btn-secondary wlDlBtn" tabindex="0" id="excelbtn{{i}}{{pwe}}" *ngIf="item.type==='excel'" type="button"
                    placement="top-left" attr.aria-label="{{'Document '+item.code}} ">
              <span>{{'Document - '+item.code}}</span>
            </button>
            <br>
          </span>
        </div>
    </div>
    

    Also, something else I noticed: you have tabindex="0" set on a native <p> tag. Per https://www.a11yproject.com/posts/2021-01-28-how-to-use-the-tabindex-attribute/: "There is a myth that applying tabindex="0" to non-interactive elements (paragraphs, headings, lists, etc.) helps “improve” accessibility by providing a way for a person who uses a screen reader to focus on all the content present in a webpage or web app. Unfortunately, this well-intentioned idea actually does not create a good assistive technology experience."

    As a best practice, I would advise you to avoid using tabindex="0" on non-interactive content. Every screen reader I have experience with allows for users to scan through and access non-interactive, text content on a webpage without the requiring the use of the tabindex attribute.