Accessible Angular.js

Accessibility is about people.

Tandem bicycling at CSUN 2015

Marcy Sutton, Seattle, USA
Accessibility Engineer, Angular Core Team Member

http://marcysutton.com/gotocon

#AngularJS web apps are never accessible because even the books on it start with inaccessible code samples! Paul J. Adam (@pauljadam) January 2, 2015
#AngularJS web apps are never accessible because even the books on it start with inaccessible code samples! Paul J Adam January 2, 2015

Angular
& Me

Today

  • Material Design & a11y waffle with happy cartoon cat
  • ngAria
  • Protractor
  • Angular 2

Legend: waffle denotes a major topic in client-rendered app accessibility

Material Design

Material Design for multi-screen consistency

Material Design for Angular 1.x

Angular Material docs website

Interactivity Happy waffle: Important Topic

Toolbar

Checkbox
Switch
Button Button Button
Dogs Cats Pigs

Reachable Controls


      <md-checkbox tabindex="0" ...

              
Add tabindex

Operable Controls


      <md-checkbox tabindex="0" 
                   ng-keypress="doStuff()" ...
              
Enable keyboard events

Semantics Happy waffle: Important Topic


    Button
          

        // should be:
    
          

ARIA Happy waffle: Important Topic


      <md-checkbox tabindex="0" role="checkbox" aria-checked="true">
        Checkbox
      </md-checkbox>
          
Roles, states and properties

Text Alternatives Happy waffle: Important Topic


   Menu
          

   
   
          

   
          

   Stop waffling and get on with it
          

Enforcing text alternatives

Angular Material Radio Buttons opens in a new window

Fixing ARIA warnings


  <md-radio-group ng-model="data.group3">
    <md-radio-button ng-repeat="it in avatarData"
                     ng-value="it.value">
        <md-icon md-svg-icon="{{it.id}}"></md-icon>
    </md-radio-button>
  </md-radio-group>

              

aria-label is missing.


  <md-radio-group ng-model="data.group3">
    <md-radio-button ng-repeat="it in avatarData"
                     ng-value="it.value"
                     aria-label="{{it.title}}">
        <md-icon md-svg-icon="{{it.id}}"></md-icon>
    </md-radio-button>
  </md-radio-group>

              

Boom! Labeled radio buttons.

Focus management Happy waffle: Important Topic

Focus management


  <md-sidenav class="md-sidenav-right" md-component-id="right">
    <md-content ng-controller="RightCtrl">
      <form>
        <md-input-container>
          <label for="testInput">Test input</label>
          <input type="text" id="testInput"
                 ng-model="data" md-sidenav-focus>
        </md-input-container>
      </form>
      <md-button ng-click="close()" class="md-primary">
        Close
      </md-button>
    </md-content>
  </md-sidenav>
          
Angular Material Sidenav with custom focus

Notifying the User Happy waffle: Important Topic

Angular Material Autocomplete with 4 Matches

Notifying the User (code)


  <aria-status role="status" aria-live="assertive">
    <p ng-repeat="message in messages">{{message}}</p>
  </aria-status>
          

  switch (self.matches.length) {
    case 0:  return 'There are no matches available.';
    case 1:  return 'There is 1 match available.';
    default: return 'There are ' + self.matches.length + ' matches available.';
  }
          
Angular Material Autocomplete Source

Angular ❤ Open Source

Angular on Github

ngAria

Accessibility Module in Angular 1.3+

https://docs.angularjs.org/guide/accessibility

Including ngAria


  angular.module('app', ['ngAria'], function($ariaProvider) {
    $ariaProvider.config({
      ariaHidden: false
    ...
          

    <body>
      <!-- page content here -->
      <script src="angular.min.js"></script>
      <script src="angular-aria.js"></script>
    </body>
          

ngAria

Adds support to these directives:

  • ngModel
  • ngDisabled
  • ngShow
  • ngHide
  • ngClick
  • ngDblClick
  • ngMessages

ngAria & ngDisabled


  <md-checkbox ng-disabled="true" aria-disabled="true"...
            

  .directive('ngDisabled', ['$aria', function($aria) {
    return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
  }])
            
ngAria Source for ngDisabled for ngDisabled opens in a new window

ngClick


      <div ng-click="ohNoYouDidnt()"></div>
            

ngAria & ngClick


 .directive('ngClick',['$aria', function($aria) {
    return {
      compile: function(scope, elem, attr) {
        var nodeBlackList = ['BUTTON','A','INPUT','TEXTAREA','SELECT'];
        if (!isNodeOneOf(elem, nodeBlackList)) {
          if (!elem.attr('role') && config.buttonRole) {
            elem.attr('role', 'button');
          }
          if (config.bindKeypress) {
            elem.on('keypress', function(event) {
              if (event.keyCode === 32 || event.keyCode === 13) {
                scope.$apply(callback);
              ...
					
https://github.com/angular/angular.js/pull/10318 Opens in a new window

That's a lot of effort...

Just use buttons!

Happy waffle: Important Topic

Protractor: end-to-end testing for AngularJS

  • Node.js command line application
  • Runs on WebDriver
  • Choose your test framework
  • Great for continuous integration

Protractor A11Y Plugin

Test your site with:

Command Line Accessibility Tests
https://github.com/angular/protractor Link opens in a new window

Protractor A11Y Plugin: Setup


  exports.config = {
    plugins: [{
      tenonIO: {
        options: {
          // options.src will be added by the test
        },
        printAll: false,
      },
      chromeA11YDevTools: true,
      path: 'node_modules/protractor/plugins/accessiblity'
    }]
  }
          
Plugin Documentation Link opens in a new window

Protractor Accessibility

Can I use?

Library Pricing API Key External Request No. of Tests
Chrome Accessibility Developer Tools Free No No 14
Tenon.io Free limited accounts, paid subscriptions Yes Yes 63

Automated Testing Strategies

  • Check for Labels
  • Validate Roles
  • Watched ARIA Properties
  • Interactions
  • Color Contrast

Angular 2

http://angular.io

Changes in Angular 2

Event bindings


      <button ng-focus="anticipationEvent()"
              ng-click="deliverWaffles()" id="{{item.id}}">
        Give me waffles </button>
          
Angular 1: attributes

      <button (focus)="anticipationEvent()"
              (click)="deliverWaffles()" [waffle-id]="item.id">
        Give me waffles </button>
          
Angular 2: properties

Angular 2 includes ARIA support

Angular 2 on Github

Contribute!

https://github.com/angular/angular Link opens in a new window

Resources

All links open in new windows

The End. waffle