Improve this Doc

Accessibility with ngAria

The goal of ngAria is to improve Angular's default accessibility by enabling common ARIA attributes that convey state or semantic information for assistive technologies used by persons with disabilities.

Including ngAria

Using ngAria is as simple as requiring the ngAria module in your application. ngAria hooks into standard AngularJS directives and quietly injects accessibility support into your application at runtime.

angular.module('myApp', ['ngAria'])...

Using ngAria

Most of what ngAria does is only visible "under the hood". To see the module in action, once you've added it as a dependency, you can test a few things:

Supported directives

Currently, ngAria interfaces with the following directives:

ngModel

Much of ngAria's heavy lifting happens in the ngModel directive. For elements using ngModel, special attention is paid by ngAria if that element also has a role or type of checkbox, radio, range or textbox.

For those elements using ngModel, ngAria will dynamically bind and update the following ARIA attributes (if they have not been explicitly specified by the developer):

Example

  Edit in Plunker
 <style>
   [role=checkbox] {
     cursor: pointer;
     display: inline-block;
   }
   [role=checkbox] .icon:before {
     content: '\2610';
     display: inline-block;
     font-size: 2em;
     line-height: 1;
     vertical-align: middle;
     speak: none;
   }
   [role=checkbox].active .icon:before {
     content: '\2611';
   }
   pre {
     white-space: pre-wrap;
   }
 </style>
<div>
<form ng-controller="formsController">
 <some-checkbox role="checkbox" ng-model="checked" ng-class="{active: checked}"
   ng-disabled="isDisabled" ng-click="toggleCheckbox()"
   aria-label="Custom Checkbox" show-attrs>
   <span class="icon" aria-hidden="true"></span>
   Custom Checkbox
 </some-checkbox>
</form>
</div>
<script>
 var app = angular.module('ngAria_ngModelExample', ['ngAria'])
 .controller('formsController', function($scope){
   $scope.checked = false;
   $scope.toggleCheckbox = function(){
     $scope.checked = !$scope.checked;
   }
 })
 .directive('someCheckbox', function(){
   return {
     restrict: 'E',
     link: function($scope, $el, $attrs) {
       $el.on('keypress', function(event){
         event.preventDefault();
         if(event.keyCode === 32 || event.keyCode === 13){
           $scope.toggleCheckbox();
           $scope.$apply();
         }
       });
     }
   }
 })
 .directive('showAttrs', function() {
   return function($scope, $el, $attrs) {
     var pre = document.createElement('pre');
     $el.after(pre);
     $scope.$watch(function() {
       var $attrs = {};
       Array.prototype.slice.call($el[0].attributes, 0).forEach(function(item) {
         if (item.name !== 'show-$attrs') {
           $attrs[item.name] = item.value;
         }
       });
       return $attrs;
     }, function(newAttrs, oldAttrs) {
       pre.textContent = JSON.stringify(newAttrs, null, 2);
     }, true);
   }
 });
</script>

ngAria will also add tabIndex, ensuring custom elements with these roles will be reachable from the keyboard. It is still up to you as a developer to ensure custom controls will be accessible. As a rule, any time you create a widget involving user interaction, be sure to test it with your keyboard and at least one mobile and desktop screen reader.

ngValue and ngChecked

To ease the transition between native inputs and custom controls, ngAria now supports ngValue and ngChecked. The original directives were created for native inputs only, so ngAria extends support to custom elements by managing aria-checked for accessibility.

Example

<custom-checkbox ng-checked="val"></custom-checkbox>
<custom-radio-button ng-value="val"></custom-radio-button>

Becomes:

<custom-checkbox ng-checked="val" aria-checked="true"></custom-checkbox>
<custom-radio-button ng-value="val" aria-checked="true"></custom-radio-button>

ngDisabled

The disabled attribute is only valid for certain elements such as button, input and textarea. To properly disable custom element directives such as <md-checkbox> or <taco-tab>, using ngAria with ngDisabled will also add aria-disabled. This tells assistive technologies when a non-native input is disabled, helping custom controls to be more accessible.

Example

<md-checkbox ng-disabled="disabled"></md-checkbox>

Becomes:

<md-checkbox disabled aria-disabled="true"></md-checkbox>

You can check whether a control is legitimately disabled for a screen reader by visiting chrome://accessibility and inspecting the accessibility tree.

ngRequired

The boolean required attribute is only valid for native form controls such as input and textarea. To properly indicate custom element directives such as <md-checkbox> or <custom-input> as required, using ngAria with ngRequired will also add aria-required. This tells accessibility APIs when a custom control is required.

Example

<md-checkbox ng-required="val"></md-checkbox>

Becomes:

<md-checkbox ng-required="val" aria-required="true"></md-checkbox>

ngShow

The ngShow directive shows or hides the given HTML element based on the expression provided to the ngShow attribute. The element is shown or hidden by removing or adding the .ng-hide CSS class onto the element.

In its default setup, ngAria for ngShow is actually redundant. It toggles aria-hidden on the directive when it is hidden or shown. However, the default CSS of display: none !important, already hides child elements from a screen reader. It becomes more useful when the default CSS is overridden with properties that don’t affect assistive technologies, such as opacity or transform. By toggling aria-hidden dynamically with ngAria, we can ensure content visually hidden with this technique will not be read aloud in a screen reader.

One caveat with this combination of CSS and aria-hidden: you must also remove links and other interactive child elements from the tab order using tabIndex=“-1” on each control. This ensures screen reader users won't accidentally focus on "mystery elements". Managing tab index on every child control can be complex and affect performance, so it’s best to just stick with the default display: none CSS. See the fourth rule of ARIA use.

Example

.ng-hide {
  display: block;
  opacity: 0;
}
<div ng-show="false" class="ng-hide" aria-hidden="true"></div>

Becomes:

<div ng-show="true" aria-hidden="false"></div>

Note: Child links, buttons or other interactive controls must also be removed from the tab order.

ngHide

The ngHide directive shows or hides the given HTML element based on the expression provided to the ngHide attribute. The element is shown or hidden by removing or adding the .ng-hide CSS class onto the element.

The default CSS for ngHide, the inverse method to ngShow, makes ngAria redundant. It toggles aria-hidden on the directive when it is hidden or shown, but the content is already hidden with display: none. See explanation for ngShow when overriding the default CSS.

ngClick and ngDblclick

If ng-click or ng-dblclick is encountered, ngAria will add tabindex="0" to any element not in a node blacklist: Button Anchor Input Textarea Select Details/Summary To fix widespread accessibility problems with ng-click on div elements, ngAria will dynamically bind a keypress event by default as long as the element isn't in the node blacklist. You can turn this functionality on or off with the bindKeypress configuration option. ngAria will also add the button role to communicate to users of assistive technologies. This can be disabled with the bindRoleForClick configuration option. For ng-dblclick, you must still manually add ng-keypress and a role to non-interactive elements such as div or taco-button to enable keyboard access.

Example

html <div ng-click="toggleMenu()"></div> Becomes: html <div ng-click="toggleMenu()" tabindex="0"></div>

ngMessages

The new ngMessages module makes it easy to display form validation or other messages with priority sequencing and animation. To expose these visual messages to screen readers, ngAria injects aria-live="assertive", causing them to be read aloud any time a message is shown, regardless of the user's focus location.

Example

<div ng-messages="myForm.myName.$error">
  <div ng-message="required">You did not enter a field</div>
  <div ng-message="maxlength">Your field is too long</div>
</div>

Becomes:

<div ng-messages="myForm.myName.$error" aria-live="assertive">
  <div ng-message="required">You did not enter a field</div>
  <div ng-message="maxlength">Your field is too long</div>
</div>

Disabling attributes

The attribute magic of ngAria may not work for every scenario. To disable individual attributes, you can use the config method. Just keep in mind this will tell ngAria to ignore the attribute globally.

  Edit in Plunker
 <div ng-click="someFunction" show-attrs>
   &lt;div&gt; with ng-click and bindRoleForClick, tabindex set to false
 </div>
<script>
 angular.module('ngAria_ngClickExample', ['ngAria'], function config($ariaProvider) {
   $ariaProvider.config({
     bindRoleForClick: false,
     tabindex: false
   });
 })
 .directive('showAttrs', function() {
   return function(scope, el, attrs) {
     var pre = document.createElement('pre');
     el.after(pre);
     scope.$watch(function() {
       var attrs = {};
       Array.prototype.slice.call(el[0].attributes, 0).forEach(function(item) {
         if (item.name !== 'show-attrs') {
           attrs[item.name] = item.value;
         }
       });
       return attrs;
     }, function(newAttrs, oldAttrs) {
       pre.textContent = JSON.stringify(newAttrs, null, 2);
     }, true);
   }
 });
</script>

Common Accessibility Patterns

Accessibility best practices that apply to web apps in general also apply to Angular.

Additional Resources