Hawk's By Design

This is going to sound terrible, but coding a site to be accessible can be a real drag sometimes. There. I said it. I’m a horrible person.

There is no disputing the fact that accessibility is a key aspect of coding a website, but that doesn’t mean I have to like it. There are times when things work out perfectly, and I’m surprised, and then other times when I spend hours of my day working with something that I thought was already done.

Keyboard Closeup

In light of my recent struggles, here are some random ass things that are necessary from an accessibility standpoint, but are a pain in the ass.

Form Labels

Have you ever styled a radio button or checkbox? The only real way to customize it is to fake it a bit.

This section is only relevant if you visually hide the input for custom styling purposes. If you have a normal radio button or checkbox setup, this is not necessary.

Basically, you hide the actual input from the user, and either wrap it in a label or use the label’s for attribute to link the two up. If you do it correctly, clicking on the label still interacts with the input, which is what you want. Then you are free to use css and pseudo-elements to make a bitchin’ checkbox or radio button.

<!-- Not keyboard accessible -->
<label>
    Your Label Text
    <!-- Visually hidden input -->
    <input type="radio" name="Test" value="Foo" />
</label>

There are a couple of accessibility issues with this. Yes, the label is marked up correctly, but that label is not inherently tabbable. You have to add a tabindex on the label so that it can be focused via keyboard. Secondly, you need to detect key inputs on that label. It’s not like a button where you get the functionality for free, you have to detect a space or enter and react to it.

<script>
    function LabelClick(e) {
        if(e.which === 32 || e.which === 13) { // space, enter
            // Stop default behavior
            e.preventDefault();
            // Simulate click on element
            e.target.click();
        }
    }
</script>
<label tabindex="0" onkeydown="LabelClick(event)">
    Your Label Text
    <!-- Visually hidden input -->
    <input type="radio" name="Test" value="Foo" />
</label>

Now, it’s easy enough to declare some sort of global function for this. Add the appropriate attribute to the label to link it up, and there you have it. Though this is relatively simple, it still pisses me off.

Modals/Popovers

Shivers.

Modals, or any sort of popover, is just a bad time. You already probably spent a lot of time creating the beautiful element, and now you get to go through hell to make it as accessible as possible.

The main points, I would say, would be:

  • Proper roles / aria attributes
  • Focus
  • Keyboard Navigation

Proper Roles / Aria Attributes

Honestly, roles and aria still confuses me, but I’m trying my best. A standard modal may look something like the following:

<aside role="dialog" aria-modal="true" aria-labelledby="ModalTitle" aria-describedby="ModalDescrip">
    <!-- Heading with id ModalTitle -->
    <!-- Text with id ModalDescrip -->
    <!-- All your other modal stuffs -->
</aside>

The modal itself is given a role that is set to dialog. This lets screen readers know that this element is apart from the rest of the content. There’s also the aria-modal attribute, which I just learned about, and this lets the screen reader know that everything under the dialog element is currently unavailable for any sort of interactions.

The next aria attribute I have is labelledby, which you point to a specific element via the id. This is used to basically give the modal a title of sorts. Going along with that, I have describedby, which gives the modal a nice description. Could you do without these two? Maybe, but might as well be thorough, right?

<button type="button" title="Click to open an awesome modal">Open Modal</button>

However you decide to trigger the modal should describe what it’s doing as well. Honestly, it really should be a button, as that natively has some nice keyboard navigation. I gave the button a title that lets the user know what’s going to happen when it’s pressed. Nothing fancy.

Focus

Focus is very important.

The modal, more than likely, is located at the end of your document, right? Or, if you are doing some fun dynamic stuff, it may only exist in the DOM at certain points. Either way, it’s not going to inherently be in the right spot in the tab, or focus, order.

Once our trigger, the button or whatever is used, is clicked, the focus needs to be put inside of the modal. All of my modals usually have a close button in the top right or something like that. As soon as the modal opens, I give focus to that element, and then take it away.

function OpenModal() {
    var closeBtn = document.getElementById('CloseBtn');
    closeBtn.focus(); closeBtn.blur();
    // Other open modal stuffs
}

What this does is it makes that close button the last focused element, so when the user tabs, the element they tab into is the first interactive element in the modal. This is great if the modal has a form, because it generally means that the first tab focuses on the first input.
The trickier part is when the modal closes. After it closes, the focus needs to be put back on whatever trigger opened it. If it’s a static page with only one trigger, then you could easily just do the focus and blur trick on that element.

If there happens to be more than one trigger, and you need to know specifically which one was clicked, then the easiest way would be to store that element whenever it is first clicked. So, whenever you are doing the open modal process, save the current active element, which would be the trigger just clicked.

var activeEl;
function OpenModal() {
    // When the trigger/button is clicked to open the modal
    // it is the current focused element on the page
    activeEl = document.activeElement;
    // Other open modal stuffs
}
function CloseModal() {
    activeEl.focus();
    // Other close modal stuffs
}

Keyboard Navigation

Piggybacking off of focus is keyboard navigation; specifically the escape and tab keys.

It’s pretty standard at this point to be able to escape out of a modal. This is pretty simple to implement. When the modal opens, add a listener to the document and determine if the escape key is pressed. If it is, start the close modal process.

function ModalKeyboardHandle(e) {
    if(e.which === 27) { // escape
        // Close the modal
    }
}
function OpenModal() {
    document.addEventListener('keydown', ModalKeyboardHandle);
    // Other open modal stuffs
}
function CloseModal() {
    document.removeEventListener('keydown', ModalKeyboardHandle);
    // Other close modal stuffs
}

Tabbing is the more difficult one. Not only do you have to check for tabbing, but also shift tabbing. You have to keep focus within the modal, so you need to know when the first and last elements of the modal are focused. That way, you can react properly.

For example. If the last element of the modal is focused, and the user hits tab, you need to prevent the default behavior and focus the first element. Same goes for if the first element is focused and the user shift tabs, then you need to focus the last element.

function ModalKeyboardHandle(e) {
    var activeEl = document.activeElement;
    var firstEl = document.querySelector('theFirstElementInModal');
    var lastEl = document.querySelector('theLastElementInModal');
    if(e.which === 27) { // escape
        // Close the modal
    }
    if(e.which === 9) { // tab
        if(e.shiftKey && firstEl === activeEl) { // shift + tab and first element
            // Prevent default tab
            e.preventDefault();
            // Give focus to last element instead
            lastEl.focus();
        }
        if(!e.shiftKey && lastEl === activeEl) { // shift + tab and first element
            // Prevent default tab
            e.preventDefault();
            // Give focus to first element instead
            firstEl.focus();
        }
    }
}

This can vary a great deal, but the concept is the same. While you are checking the user’s keystrokes, make sure to store the first and last element of the modal (or, you can do this during the open modal process). When you register a tab, check to see if the shift key is active. Then, check to see if the active element matches the first or last element based off if tab is active.

If you get a match, prevent the default behavior and give focus to either the first or last element, whichever is applicable.

This can get complicated if you have a last tabbable element that can be disabled, like a form submit button. If that’s the case, you need to add a check. If it’s disabled, find a way to get the second to last element.

Learning

I think the biggest reason accessibility gets me so riled up is because there is so much of it, and so much of it conflicts. In one example you are told to do it one way, and in the other another.

I do my best to make sure my site’s are accessible. Are they perfect? Most likely not. I think that’s okay though. Honestly, there are so many sites out there that aren’t accessible, I believe a person would appreciate the effort.

Of course…if it’s really bad they might just get annoyed…

Conclusion

After all of this writing, I’m basically just bitching about something that I have to do because it’s part of my job. I guess the worst part of it is just the lack of reward. When you code CSS, or JavaScript, or anything else, you can visually see stuff changing. Accessibility stuff is, for the most part, never seen.

Oh. One last tip. If you have a dropdown or something to that effect, it’s super easy to adjust your css off of the aria’s expanded attribute. Don’t be a butthead and use a class.

[aria-expanded="true"] {
    /* Dropdown is open */
}

Thanks for listening to me whining like a baby.

Some more articles for you

Coding

November 29, 2018

Webp – What is it? Why should you care?

Images make up a crazy amount of content on the web. WebP is a new lossy and lossless image format aimed at better compression, but same quality.

View post

Coding

February 25, 2019

NG-IF: My Two Hour Headache

This past weekend I learned a valuable lesson about AngularJS and the ng-if directive. So, naturally there was a lot of cursing and crying.

View post