usds/us-forms-system

Text and inputs added dynamically should be announced by screen readers

Opened this issue · 14 comments

Audit Information

  • Date: September 17, 2018
  • OS: Windows 7 64 bit Enterprise, Service Pack 1
  • Browser: Internet Explorer 11
  • Screen Reader: JAWS 2018

Describe the bug
New inputs or conditional text added to the DOM based on user interaction with radio buttons, checkboxes, or select menus need to be announced to screen readers so users have context that new information is available.

To Reproduce

  1. Review a form with radio buttons or checkboxes that conditionally expose additional content. This might be text or an input field.
  2. Verify the new information or fields are not read out by the screen reader

Expected behavior
The new information should be read out by the screen reader. We want users to know the form or view has changed, and additional contextual information is being presented. Screenshots below show a few instances of this pattern.

Screenshots
screen shot 2018-09-20 at 10 26 48 pm


screen shot 2018-09-20 at 10 30 15 pm

@1Copenut What would be the preferred way to do this? Should we use role="alert" on the elements that appear underneath the checkbox or radio button?

@dmethvin-gov Reviewing a few docs and the spec, I think it'd be beneficial to add role="status" and aria-live="polite" like the following pseudo-code snippet:

<div class="schemaform-widget-wrapper">
  <div>
    <div aria-live="polite" class="form-expanding-group form-expanding-group-open" role="status">
      <!-- radio button and expanded content here -->
    </div>
    <div class="form-expanding-group">
      ...
    </div>
    <div class="form-expanding-group">
      ...
    </div>
  </div>
</div>

I'm sourcing this from MDN: Preferring Specialized Live Region Roles and ARIA19: Using ARIA role=alert or Live Regions to Identify Errors.

I was able to test this successfully in Safari + VoiceOver and Chrome + JAWS, but was not able to test with NVDA or IE11 + JAWS, so it will need further analysis before release. My fear is the redundancy will read the added content twice, but hopefully not.

I'm starting to work on a PR for this, but I haven't had much success with the live region approach. In VoiceOver it re-reads the existing content in the region and then reads the new content twice. I've tried different variations of aria-atomic and aria-relevant and haven't gotten anywhere. I had similar issues with a Preact based expanding group component for another project.

Another common pattern for expanded content is aria-expanded, where screen readers just announce if content is expanded or collapsed below. We can't use that attribute directly on inputs, but I think we could come up with an approach that caused a status message to be read when new content was rendered (e.g. "Additional information expanded below current question").

What do you think of that approach, @1Copenut and @dmethvin-gov?

@jbalboni do you have a test built? I haven't had experience with adding ARIA attributes but wonder if the way we have the React components generate content may be confusing the reader. For example, rather than conditionally render an entire section and its content, always render the section but leave it empty unless there is content. An example at a site like codesandbox.io might let us experiment with that. You could fork off of this one which is 1.1.0.

I was just testing it locally on one of the VA.gov forms, but I can try to set it on that codesandbox.io site.

I was going off of Trevor's example, where the aria-live attribute isn't on a div that's being conditionally rendered. My understanding is that adding and removing elements with aria-live doesn't work, you have to add and remove content within those elements. It could be related to the css transition group component, but I remember removing that in my other project and still not making it work. My suspicion is that it's something related to how React updates elements, but not really sure.

@jbalboni I was hoping it'd be an easy fix with ARIA, but it looks like we'll have to take another tack. You're right, aria-live has to be on an element in the DOM on first paint, or screenreaders won't announce the new content.

I wonder if we could try an approach where we move focus to the container React adds to the DOM when the animation is complete. So in the screenshot above, when the radio button finished expanding, we would focus the <div tabIndex="-1"> that wraps text "You are applying as the legally married spouse...". This might not be in keeping with the form system ethos, so I'll ask and see where exploration leads.

@dmethvin-gov Just getting back to this now. It doesn't look like I can get codesandbox.io to reference a GH dependency, so I can't show exactly what I was testing. Here's where I was making changes in the ExpandingGroup component: 1.x-stable...jbalboni:announce-new-content-jsb. I was using a basic expandUnder field to test it.

@1Copenut I think the issue with that approach is when we have expandable content underneath a group of radio buttons. If you are moving through the options with the down arrow, you'll have your focus hijacked and have to tab back up if you were trying to select a later option.

@jbalboni Ok I see the problem, the ExpandingGroupElement isn't something that can be easily controlled or substituted for testing. Ugh.

@jbalboni

My understanding is that adding and removing elements with aria-live doesn't work, you have to add and remove content within those elements.

This is spot on. The container with an aria-live attribute has to be in the DOM on first render, or it won't read out content that's added later. Depending on how the CSS transition group is being used, that could also have an effect. display: none to display: block could be causing it not to read out. I'm less certain about that.

You're also spot on about moving focus to the dynamically added container -- that would break the user's mental model of how radio buttons work. We need a way to have that content read out, without disrupting focus.

aria-live should work, but without a code sample to try out, I'm guessing somewhat. I'll have to stand up the repo next week so I can try out your branch.

@1Copenut Yeah, if you could try out the branch I linked, that'd be helpful. I couldn't make the aria-live approach work correctly on my first pass.

I'd still like to get your opinion on if an approach that lets users know about additional content (vs reading out the additional content) is possible. It's a much easier task to have an aria-live region that we use as a place to put status messages (similar to what Downshift does for certain things).

@jbalboni that repo link doesn't work for me, is it a private repo?

@dmethvin-gov No, looks like the link broke somehow. Should work now.

@jbalboni Will do. I've got a couple of pressing items queued up, so I'll get to this ASAP.

I'm doing some initial testing, and it's a mixed bag, but does seem we're on the right path. Using @jbalboni solution linked above, here's what I've found:

  1. MacOS + Safari + VoiceOver reads out the added text and input, on the first time it's painted to the DOM. If I continue toggling through radio buttons, it seems to "forget" to read it out. First run through however, is solid.
  2. Windows + Chrome + JAWS 2019 reads out the added text, but has an annoying habit of reading out the entire <select> menu as well, no matter how many <option> choices there are. So this pattern holds up pretty well for text only, but really gets tricky when we're adding selectable inputs inside radio buttons. Which does bring up a potential issue for keyboard users -- interacting with inputs inside a form input like a radio button could break the browser's default rendering. Not the issue we're capturing here, but worth mentioning.
  3. Windows + NVDA + Firefox is not reading any of the added text or inputs out. I'm trying a few things in markup to see if I can remediate the issue.