On Github marcysutton / a-web-for-everyone
Everyone can perceive, understand, navigate, and interact with the Web, and they can contribute to the Web.
Millions of people have disabilities that affect their use of the Web.
Web accessibility also benefits people without disabilities.
Refer to slide #2 about the kinds of apps we build.
Marcy Sutton, Seattle, USADeveloper @Substantial, Angular Material Contributor
<md-button tabIndex="-1"> <button>Flat Button</button> </md-button>
Expands HTML's semantic vocabulary
// ngAria - ngModel directive function getRadioReaction() { if (needsTabIndex) { needsTabIndex = false; return function ngAriaRadioReaction(newVal) { var boolVal = newVal === attr.value; elem.attr('aria-checked', boolVal); elem.attr('tabindex', 0 - !boolVal); }; ...
What does this thing do?
<div role="img" style="background-image...">
The current condition of this particular thing
<md-input-group aria-disabled="true">
<md-checkbox aria-label="Unsubscribe">
Disables a screen reader’s “virtual cursor”
<body role="application"></body>
<div ng-click="sorryKeyboards()"></div> // Non :(
<button ng-click="woohoo()"></button> // Oui! :)
<md-button role="button" tabIndex="0" ng-click="woohoo()" ng-keypress="woohoo()"> </md-button>Challenge the concept of "interactive". It doesn't have to be visually interactive. Appeal to all the senses. PERCEIVABLE and OPERABLE. And robust. And understandable.
[tabIndex="0"] { color: $linkColor; &:focus, &:hover { background-color: $linkColor; color: $linkActiveColor; } }
$scope.$watch('airportIsSelected', function(newValue){ if(newValue){ $scope.pickerIsVisible = true; $('html,body').animate({ scrollTop: peoplePicker.offset().top }, 600); peoplePicker.find('button').first().focus(); $rootScope.$broadcast('statusUpdated', self.selectedAirport); } });
<live-region level="polite"></live-region>
function liveRegionDirective($scope, $element, $attrs) { element.attr({ 'role' : 'region', 'aria-live': $attrs.level }); $scope.$on('statusUpdated', function(scope, message) { $element.html(message.text); }); }
// material.components.checkbox function MaterialCheckboxDirective($materialAria, ...) { tAttrs.tabIndex = 0; tElement.attr('role', 'checkbox'); return function postLink(...) { $materialAria.expectAttribute(element, 'aria-label'); function keypressListener(ev) { if (element[0].hasAttribute('disabled')) return; ...
// material.services.aria function expectAttribute(element, attrName, defaultValue) { var node = element[0]; if (!node.hasAttribute(attrName)) { var hasDefault = angular.isDefined(defaultValue) && defaultValue.length; if (!hasDefault) { $log.warn(messageTemplate, attrName, getTagString(node)); $log.warn(node); } else { element.attr(attrName, defaultValue.trim()); } } }
// chrome://accessibility/ AXCheckBox AXRoleDescription='check box' AXEnabled='1' AXFocused='0' AXRequired='0' AXTitle='Checkbox (Disabled)' AXValue='0' AXDescription='Disabled checkbox'
// material.components.checkbox function MaterialCheckboxDirective($materialAria, ...) { tAttrs.tabIndex = 0; tElement.attr('role', 'checkbox'); scope.$watch('disabled', function(newValue){ element.attr('aria-disabled', newValue); }) ...
// material/components/radioButton.spec.js describe('radioButton', function() { beforeEach(module('material.components.radioButton')); it('should be operable via arrow keys', inject(function($compile, $rootScope) { var element = $compile( '<md-radio-group ng-model="color">' + '<md-radio-button value="blue"></md-radio-button>' + '<md-radio-button value="green"></md-radio-button>' + '</md-radio-group>' )($rootScope); $rootScope.$apply(function(){ $rootScope.color = 'blue'; }); var rbGroupElement = element.eq(0); TestUtil.triggerEvent(rbGroupElement, "keydown", {keyCode: Constant.KEY_CODE.RIGHT_ARROW}); expect($rootScope.color).toEqual('green'); })); }); ... // material/config/test-utils.js var TestUtil = { triggerEvent: function (element, eventName, eventData) { eventData = eventData || {}; var e = $.extend({}, $.Event(eventName), eventData); if(eventData.keyCode){ e.which = eventData.keyCode; } element.trigger(e); } }
Angular Accessibility@marcysutton / MarcySutton.com