screenshot

What is Accessibility?

A wesbite that is accessibile can be used by "all people, regardless of dissability". A website that is inaccessible is not only one that is turning away potential auidence members and customers, it is also denying them a chance to participate.

When designing a website with accessibility in mind it is helpful to putyourself in the shoes of others. You may find it useful to ask yourself questions along the lines of:

For more information on web accessibility, as well as best practices on addressing accessibility in web design we recommend reading W3C Web Content Accessibility Guidelines.

Why we had to write our own accessibility checker

When we first started looking into accessibility for datayze we realized that our site was a little different. One of the key issues was the fact that we were building interactive tools. Not only did the inital tool interface need to be accessible, if the tool interface changes as a result of user input we also need to insure the new interface is also accessible. Most accessibility checkers out there were only analyzing the static content and ignoring the generated content. Most of the tools out there just weren't sufficient for assuring datayze was accessible.

The second issue we encountered is that we are almost constantly in a development cycle creating new tools. We wanted a tool that could automatically be checking accessibility as we code, that could catch easily correctable issues between full accessibility autits.

Thus We decided to write our own automated tool to serve as a sanity check. This article is intended for web developers who may need a little more comtrol over their automated accessibility checks. The code presented here may be sufficient for you, but there may be other use cases we haven't considered because they're not relevant to Datayze. No automated tool will be a perfect solution.

I'm going to say it again because it's so important: The code in this article is not intended to take the place of an accessibility audit!

Our approach

For our approach we wrote a JavaScript application that runs on our test server. The script listens to when the Document Object Model (DOM) has changed, and tests any new entites and gives a warning whenever a potential issues is encountered. It's helped us realize we accidently gave a input field and aria_label instead of the properly defined aria-label!

Automated Accessibility Checks

The following code uses jQuery, so be sure not to forget your include!

<script src="jquery-3.4.1.min.js"> </script>

Also, we should point out that our method of writing to the console is a little clumsy. We're choosing to display each element that generates a warning as nodeName.id.class, but some elements do not have ids or classes. Thus we may see something like ---- Accessibility Warning: Select.runrate.undefined ... in our console log. For us, that's good enough to track down the offending element, especially since we're only running this code in our testing enviornment. You may choose to log your issues rather than display them on the console.

Alt Tags

One of the first things that usually springs to mind when we discuss accesibility is alt tags. (Fun side note, the alt stands for "Alternative information.") The alt tag provides an alternative information for those who can't or don't want to get the information from the image itself. for this usecase we write the function checkAltTags().

function checkAltTags(subselect){ var interactable = $(subselect).find('img:visible').filter(function() { return ($(this).attr('alt') == undefined); }).each(function (){ console.log("---- Accessibility Warning: " + $(this).prop('nodeName') + '.' + $(this).attr('id') + '.' + $(this).attr('class') + ' No ALT tag -----'); }); }

The first thing checkAltTags() does is look for all visible images (img) (line 2) and then filter if the alt attribute is undefined.

Tab Navigation

One way we can navigate a website with a keyboard rather than a mouse is through the tab key. Users who rely on their keybards to navigate websites will often use tab clicks to select the link they want, and then press enter to surf to that link. We want our users to be able to use the tab/enter key combo in place of any clickable event. We do this by creating a function checkTabAccessibility().

checkTabAccessibility() works by looking at all elements in a subselect (We'll get to how the subselect is defined later) and filters out all elements that are not concerning. The ones that are left are the ones we need to be notified about

At datayze we use the pointer cursor to indicate an item is clickable. The first thing that checkTabAccessibility() does is filter out all elements where the cursor is not a pointer (line 3). Each item left is one we want to be reachable via a tab. That means that attribute 'tabindex' must be defined if the element is not tabable by defualt. We do this check in isAccessable(). Inputs and A link items are tabable by default. Anothing else must have a tab index. If it doesn't, we check the element parents to ensure it's just not a stacking issue. (An eample where this happens on Datayze be the header menu above. The cursor becomes a pointer is over the text, but the entire span encompusing the text is hooverable/clicable.)

function checkTabAccessibility (subselect){ var interactable = $(subselect).find('*').filter(function() { if ($(this).css('cursor') != 'pointer'){return false;} var $this = $(this); if (isAccessable($this)){return false;} // check this element var accessible = false; $this.parents().each(function (){ // check each of the parents accessible = accessible || (isAccessable($(this))); }); return !accessible; }).each(function (){ console.log("---- Accessibility Warning: " + $(this).prop('nodeName') + '.' + $(this).attr('id') + '.' + $(this).attr('class') + ' Can not reach with tab -----'); }); } function isAccessable(elem){ return (elem.prop('nodeName') == 'A' || elem.prop('nodeName') == 'INPUT' || elem.attr('tabindex') != undefined); }

I've seperated out isAccessable() to make it easier to modify if you want to check something other than I have.

Note, we'll discuss how to ensure the right behavor happens once an item is reached via a tab and the enter is pressed down below

Input Labels

Many of our tools have input fields for user supplied input. To be sure we are inclusive for visually impaired audiance members we need to make sure each input is properly labeled. There are several different ways we can do this. We can have an aria-label attribute or we can use a label tag. We're going to preform this check using checkInputHasLabels(). As before, we start with a subselect of HTML elements and filter out the ones that are properly accessible.

function checkInputHasLabels(subselect){ var interactable = $(subselect).find('input, select').filter(function() { // check if field visible if ($(this).attr('type') == 'hidden'){return false;} // check if element has aria-label if ($(this).attr('aria-label') != undefined){return false;} // buttons don't need to be labeled //https://www.w3.org/WAI/tutorials/forms/labels/#labelling-buttons if ($(this).attr('type') == 'button' && $(this).attr('value') != undefined){return false;} if ($(this).attr('type') == 'submit' && $(this).attr('value') != undefined){return false;} // only thing left is a label tag // can't have a label tag if no id if ($(this).attr('id') == undefined){return true;} return ($('label[for="' + $(this).attr('id') + '"]').length == 0); }).each(function (){ console.log("---- Accessibility Warning: " + $(this).prop('nodeName') + '.' + $(this).attr('id') + '.' + $(this).attr('class') + ' Input does not have label -----'); }); }

Putting it all together

Next we put it all together with a function checkAccessibility(). We give checkAccessibility() a slight delay using the setTimeout to ensure any running javascript has a chance to compelete and inish modifying the DOM before we check the results. JavaScript is single threaded, so giving the function a slight delay is equivalent to saying "do this next."

As mentioned above there is no one-size-fits-all solution to accessibility. Depending on your site you may have other checks you need to preform. If so, you'll want to add them to checkAccessibility().

function checkAccessibility(subselect){ setTimeout(function(){ checkTabAccessibility (subselect) checkAltTags(subselect); checkInputHasLabels(subselect); }, 50); }

Finally, we need to create a MutationObserver to watch for DOM changes. This is critical for datayze as sometimes our tool interfaces are finalized after the inital document ready. When the MutationObserver detects a DOM change, it builds a selector of elements that could have potentially been modified using getIdentifier(). From there, we call checkAccessibility() which preforms our checks.

function getIdentifier(mutation){ if (mutation.id != '' && mutation.id != undefined){ return '#' + mutation.id; } if (mutation.className != '' && mutation.className != undefined && (typeof mutation.className != "object")){ return '.' + mutation.className; } return mutation.localName; } if (!String.prototype.includes) { String.prototype.includes = function() { 'use strict'; return String.prototype.indexOf.apply(this, arguments) !== -1; }; } $(function() { var observer = new MutationObserver(function(mutations) { var elements = []; mutations.forEach(function(mutation) { var elem = getIdentifier(mutation.target); if (!elements.includes(elem)){elements.push(elem);} }); checkAccessibility(elements.join(',')); }); observer.observe(document.body, { childList: true, subtree: true, }); checkAccessibility(); });

Bonus Tip

In addition to using a JavaScript to check the current accessibility of your website, you can also use it to make your site more accessible at run time!

While discussing the checkTabAccessibility() function I focused soley on whether or not a clickable element could be reached via a series of tab presses. I didn't worry about whether the element had a corresponding onKeyUp or onKeyDown to match the onClick, because I handle that at run time with the following block code.

/* if you press enter on a focus element (other than an ad!! or textarea) act as a click */ $(document).on('keydown', ':focus', function (e){ // don't trigger on google ads if ($(this).parents('ins.adsbygoogle').length > 0 || this.tagName == 'INS'){return;} // already handled if ($(this).hasClass('help') || this.tagName == 'TEXTAREA' || this.tagName == 'INPUT' || this.tagName == 'BUTTON'){return;} //keycode 13 == enter if(e.keyCode == 13){ // act like a click $(this).trigger('click'); } });

When an element is reached via a tab, it becomes in focus. For Datayze tools I need to use $(document).on('keydown', ':focus', function (e){...}); to ensure the JavaScript is always listening for that keydown event on focus elements, even if the DOM changes. If the event has a keycode 13 that means the enter was pressed. In that case, I trigger a click and the element behaves as if a mouse was clicked.

Important caveat: Be very careful when triggering a click if you use any kind of ad network. You'll see I do not trigger if the element is a google ad. Automating a click is against many ad networks terms of service and could get you banned.

One thing you may have noticed is that code blocks are reachable/selectable via tabs or clicks to facility easy copying. One way to do this is to add the property tabindex="0" to each code element. Another way to achieve this is to add the following code which inserts the property automatically.
$('.txtcode, .javacode, .phpcode').prop('tabindex', '0');

Code Liscence

Although code shared on data·yze is source-avaliable, it is still proprietary and data·yze maintains it's intellectual property rights. In particular, data·yze restricts redistribution of the code. Code displayed above may be copied, modified, displayed or adapted for use on other websites (commercial or otherwise) only under certain conditions and may not be repackaged or redistributed. See Terms for details.