Log of QDelft

Tips, tricks and tools for building accessible React web apps


React

React logo
"A JavaScript library for building user interfaces." - React docs

Who uses React?

Facebook logo Netflix logo Airbnb logo Instagram logo Whatsapp logo Tenon logo Wehkamp logo BBC logo

Westworld?

Jokingly showing the television series Westworld using React as well.

How accessible are they?

Movie poster of The Good, the Bad and the Ugly

The Bad and the Ugly

The Good from the movie the Good, the Bad and the Ugly The Good from the movie the Good, the Bad and the Ugly

WebAIM Alexa Top 100 scan

"The number of errors found has increased 60% over the last 5 years – from an average of 25 errors in 2011 to 40 errors in 2017." - WebAIM

But why?

Cat confused about the accessible state of React websites

React syntax


import React from 'react';
import Content from './Content';

const Root = ({headerText, onLogout}) => (
     <main>
        <h1>{ headerText }</h1>
        <Content onLogout={onLogout}/>
     </main>
);

export default Root;

Everything in JavaScript?

Image of koala expressing shock at React being all JavaScript
Image of Harry Potter turning a div into a button

        <div className="looks-like-a-button"
             onClick={this.onClickHandler}>
               Press me please
        </div>
    

JS-Zilla destroys a11y!!

Image depicting JavaScript as a destructive monster

It can be a friendly monster...

Image depicting JavaScript as cute monster

The Good-a11y

The Good from the movie the Good, the Bad and the Ugly
Image capture and Axe scan of tenon.io
Tips and tricks header image

Tips and tricks

Example application

github.com/AlmeroSteyn/react-a11y-patterns

React component tree

Shows the React component tree with parent child relationships with data flowing down the tree and events bubbling up.

React component class


import React, { Component } from 'react';

class Demo extends Component {
    render() {
        return ( <span>{this.props.displayText}</span> );
    }
}

export default Demo;

React component class


import React, { Component } from 'react';

class Demo extends Component {
    render() {
        return ( <span>{this.props.displayText}</span> );
    }
}

export default Demo;

React component function


import React from 'react';

const Demo = ({ displayText }) => (
    {displayText}
);

export default Demo;

Using a component


import React from 'react';
import Demo from './Demo';

const Root = () => (
     <Demo displayText="Show this text"/>
);

export default Root;

React virtual DOM + Reconciler

Depicts the React virtual DOM and how it relates to the real DOM

React virtual DOM in browser

A debug view of the React DOM in React dev tools

React actual DOM in browser

A debug view of the DOM to compare to that of the React DOM of the previous slide

JSX

HTML-like syntactic JavaScript sugar.


        <label htmlFor={nameId}>{nameLabelText}</label>
        <input id={nameId} type="text" />
    
Renders to HTML in the DOM.

         
         
    

First rule of ARIA

Bad idea:


       <div className="looks-like-button"
            onClick={this.onClickHandler}>
         Press me
       </div>
     

Good idea:


         <button onClick={this.onClickHandler}>
            Press
         </button>
     

JSX and ARIA

All ARIA attributes are valid JSX props!


            <input id={nameId} aria-label={accessibleLabel} type="text" />
    

IntelliSense in supported IDEs.

ARIA attribute IntelliSense in JSX with WebStorm

Good JSX makes good HTML


const AppNavigation = () =>
  <aside>
    <nav>
      <ul className="nav">
        <li>
          <NavLink
            to={pathname}
            activeClassName="active">
            Contact
          </NavLink>
        </li>
      </ul>
    </nav>
  </aside>;

Good JSX makes good HTML


const AppNavigation = () =>
  <aside>
    <nav>
      <ul className="nav">
        <li>
          <NavLink
            to={pathname}
            activeClassName="active">
            Contact
          </NavLink>
        </li>
      </ul>
    </nav>
  </aside>;

Good JSX makes good HTML


const AppNavigation = () =>
  <aside>
    <nav>
      <ul className="nav">
        <li>
          <NavLink
            to={pathname}
            activeClassName="active">
            Contact
          </NavLink>
        </li>
      </ul>
    </nav>
  </aside>;

Fragments in React 16


const values = ['a', 'b', 'c'];

const Fragments1 = () =>
  values.map((item, index) => <li key={index}>{item}</li>);

const Fragments2 = () => [
  <tr key="1"><td>a11y</td></tr>,
  <tr key="2"><td>rocks</td></tr>
];

Intact header symantics

Diagram of HTML headers semantics. H1 to H3 hierachy depicted.

const HeaderWithLevel = ({ headerText, level }) => {
  const HeaderLevel = `h${level}`;
  return <HeaderLevel>{headerText}</HeaderLevel>;
};

Components and unique id's


 import uuid from 'uuid';
 //...
 this.inputId = uuid.v4();
 //...
 render() {
    //...
    <label htmlFor={this.inputId}>
      {labelText}
    </label>
    <input id={this.inputId}
      onChange={this.onChangeHandler}
      value={value}
    />
    //...
 }
 
github.com/kelektiv/node-uuid

Routing is a11y silent


const AppMain = () =>
  
<Switch> <Route path="/todos" component={Todos} /> <Route path="/todo" component={Todo} /> <Route path="/contact" component={Contact} /> <Redirect to="/todos" /> </Switch>
;

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>);
  }
}

Classes and refs


class WillFocus extends Component {
  someHandler() {
    //...
    this.toFocus.focus();
    //..
  }

  render() {
    return <ToFocus ref={toFocus => {this.toFocus = toFocus;}} />;
  }
}

What about HOCs?


const EnhancedComponent = higherOrderComponent(WrappedComponent);

React Router withRouter HOC:


const WrappedInReactRouter = withRouter(WrappedComponent);

React Redux connect HOC:


const WrappedWithReduxConnect =
                    connect(mapStateToProps)(WrappedComponent);

Refs and HOCs

Showing that a HOC wraps the wrapped component and therefore disallows ref access to this component.

Bypass HOC with a callback prop!


function Field({ inputRef, ...rest }) {
  return <input ref={inputRef} {...rest} />;
}

const EnhancedField = enhance(Field);

<EnhancedField
  inputRef={(inputEl) => {this.inputEl = inputEl;}}
  />

this.inputEl.focus();

Changing the document title


//...
import DocumentTitle from 'react-document-title';
//...
const SetDocTitle = ({ docTitle, children }) =>
  <DocumentTitle title={docTitle}>
    {children}
  </DocumentTitle>;
github.com/gaearon/react-document-title

ARIA live


const Announcer = ({ message }) =>
  <div aria-live="polite"
       aria-atomic="true"
       aria-relevant="additions">
    {message}
  </div>;

ARIA live and the component tree

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

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
Tools header image

Tools

create-react-app


yarn global add create-react-app
create-react-app my-react-app
cd my-react-app
yarn start
  • Webpack development and production build.
  • ESLint with some a11y rules.
  • Progressive app by default.
  • Supports code splitting.
  • Unit tests + coverage with JSDOM.

eslint-plugin-jsx-a11y

"Static AST checker for a11y rules on JSX elements."
github.com/evcohen/eslint-plugin-jsx-a11y

eslint-plugin-jsx-a11y

More than 30 ESLINT a11y checks.

Image of some rules in the eslint-jsx-a11y plugin

eslint-plugin-jsx-a11y

Rules are configurable.


       {
         "rules": {
           "jsx-a11y/rule-name": "warn"
         }
       }
    

Extending config in create-react-app.


        {
          "extends": ["react-app", "plugin:jsx-a11y/recommended"],
          "plugins": ["jsx-a11y"]
        }
    

eslint-plugin-jsx-a11y

A11y issues become build warnings.

Shows eslint-jsx-a11y error feedback in create-react-app build

eslint-plugin-jsx-a11y

IDE integration.

Shows eslint-jsx-a11y error feedback in an IDE

react-axe

"Accessibility auditing for React.js applications."
React-axe uses axe-core. Shows axe-core logo.
github.com/dequelabs/react-axe

react-axe

Setting up react-axe.


import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

if (process.env.NODE_ENV !== 'production') {
    const axe = require('react-axe');
    axe(React, ReactDOM, 1000);
}

ReactDOM.render(<App />, document.getElementById('root'));

react-axe

Browser console feedback.

Shows the react-axe console feedback for accessibility errors in Chrome

Combining the tools

Summary of console errors for React, aslint-jsx-a11y and react-axe

React a11y docs

Screenshot of the React accessibility docs reactjs.org/docs/accessibility.html
Image of captain kirk showing thumbs up to Spock. Accessibility is logical.

Questions


Presentation online at:

almerosteyn.com/slides/