Log of QDelft

Breaking the silence

Screen readers and React apps


Inclusive Design

The design of mainstream products and/or services that are accessible to, and usable by, as many people as reasonably possible... - British Standards Institute

React a11y docs

Screenshot of the React accessibility docs reactjs.org/docs/accessibility.html

The screen reader

JAWS logo
www.freedomscientific.com/Products/Blindness/JAWS
NVDA logo
www.nvaccess.org
VoiceOver logo
www.apple.com/lae/accessibility/mac/vision

The dark room

A torch lighting up a dark room
Paciello Group Blog: bit.ly/2C1zyBI

App with accessible HTML

Well, that's still unusable!

Jacky Chan very shocked about how unusable this is

Routing is silent


const AppMain = () =>
  
<Switch> <Route path="/tasks" component={Tasks} /> <Route path="/task" component={Task} /> <Route path="/contact" component={Contact} /> <Redirect to="/tasks" /> </Switch>
;

So are other important updates and messages...

Application input errors are not read out

Dearest user, you're not welcome!

Unwelcome looking gate in arid landscape with barbed wire saying keep gate closed and locked

Set focus after navigation


class PageFocusSection extends Component {
  componentDidMount() {
    this.header.focus();
  }
  render() {
    const { children, headingText } = this.props;
    return (<section>
        <h2 tabIndex="-1"
            ref={header => {this.header = header;}}>
          {headingText}
        </h2>
        {children}
      </section>);
  }
}
reactjs.org/docs/refs-and-the-dom.html

Adding Focus Control

ARIA live regions to the rescue!

Person shouting an important message through a loudspeaker developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions

ARIA live


const Announcer = ({ message }) =>
  <div role="log" 
       aria-live="polite" 
       aria-relevant="additions" 
       aria-atomic="true"> 
    {message}
  </div>;
  
  //Possible values
  //role:          log | status | alert 
  //aria-live:     polite | assertive | off
  //aria-relevant: additions | removals | text | all
  //aria-atomic:   true | false

Error announcer!


    <label htmlFor={inputId}>{labelText}</label>
    <input
      id={inputId}
      aria-invalid={showErrors}
      aria-describedby={showErrors ? describedById : null}
      onChange={onChangeHandler}
      value={value} />
    {showErrors ?
      <div
        id={describedById}
        aria-live="polite"
        aria-atomic="true">
          {errorText}
      </div> 
     : null}

Our live region in JAWS

So we are done right?

Right?!

Why are you building the tension??!!

Our live region in NVDA

Stable error announcer!


 <label htmlFor={inputId}>{labelText}</label>
 <input
   id={inputId}
   aria-invalid={!showErrors}
   aria-describedby={showErrors ? this.describedById : null}
   onChange={onChangeHandler}
   value={value} />
 <div
   aria-live="polite"
   aria-atomic="true">
   {showErrors ? 
     <span id={describedById}>{errorText}</span> 
   : null}
 </div>

ARIA live and the component tree

Illustrates that the aria live region is more stable when rendered in the root component.

Let's light up that dark room!

Light bulb plugging itself in to give more light.

ARIA live announcer


//...
import { LiveAnnouncer, LiveMessage } from 'react-aria-live';
//...
render() {
return (
  <LiveAnnouncer>
    <LiveMessage message={this.state.a11yMessage}
                 aria-live="polite" />
    {this.props.children}
  </LiveAnnouncer>
  );
}
//..
github.com/AlmeroSteyn/react-aria-live

Full screen reader a11y!

Questions


Presentation online at:

almerosteyn.com/slides/

Example app at:

github.com/AlmeroSteyn/react-a11y-patterns/tree/react-amsterdam-talk