Creating Accessible Components
The first thing developers come across when trying to learn about how to "code accessibly" is ARIA. Little do they know, ARIA is actually the last resort method we should turn to. Let's go over ways we can make our code accessible!
Semantic HTML
DIV Hell. Sometimes, we just don't know or care to look up what the best element is to use, so we fallback to a simple DIV. The problem is that the overuse of DIVs means missing out on a lot of native accessibility and functionality that semantic HTML provides:
Conveys structure and hierarchy of web pages
Allows users to skip to and navigate by sections and elements
Indicates the kind of element a user is dealing with and how to interact with it (button vs link vs input)
Require less maintenance for if browsers update their accessibility APIs
ATs try to convey this information to its users by reading the role of the element. Roles can be defined by adding a role
attribute, but semantic HTML like h2
, button
, header
, footer
, etc have inherit roles assigned to them so we don't need to take that extra step. It even goes a step further by providing additional information like heading level, tab indexing, and default keyboard handlers depending on the accessibility API provided by the browser, all of which we would need to define ourselves when using DIVs.
None of this is to say that you can't use DIVs! Using a div
or span
would be appropriate when we want to style individual pieces within a semantic element, or if no native semantic tag exists for the type of content we need.
1// Bad heading - this gives no indication to non-sighted users that this is a heading element2<div class="heading-2">Semantic HTML</div>3
4// Okay heading - we can use a role attribute to indicate this element is a heading, 5// but we also need to use aria to indicate level, which is just extra steps6<div role="heading" aria-level="2" class="heading-2">Semantic HTML</div>7
8// Best heading - using the native HTML heading tag provides both the role and level to the user, 9// and if browsers update their accessibility API to change what additional information is provided to ATs, 10// no additional dev work is needed.11<h2 class="heading-2">Semantic HTML</h2>12
13// Best heading with div layout - an example where we would want the heading to still read as one word, 14// but maybe we want the layout to be styled differently15<h2 class="heading-2 display-flex-column">16 <div>Semantic</div>17 <span class="red-text">HTML</span>18</h2>
Making your code more semantic is probably one of the easiest fixes you can start applying by simply switching some of your DIVs to something more meaningful. It's also much easier to follow along with code if you can read that the section is a header
instead of a div
, so brush up on all the available HTML tags and start applying the most appropriate ones to your code!
ARIA
If you've researched how to make code accessible, you've probably come across ARIA and maybe even started to think about adding it to all of your elements. Well don't. The first rule of ARIA is to avoid using ARIA. Semantic HTML is already natively accessible (it's often that we just aren't using or structuring it properly), but using ARIA overrides the information presented to ATs, which can inhibit users if you don't know what you are doing.
ARIA attributes are most often needed when we start adding interactions or states to our elements. We use visual design to convey to users with sight that an element is loading, has toggled something on the page, or is currently selected, but we also need to convey this information programmatically for users without sight. We can do this by adding appropriate ARIA properties that will communicate the information to ATs.
Let's use a simple accordion component as an example:
1const Accordion = () => {2 const [isOpen, setIsOpen] = useState(false);3 const toggleOpen = () => setIsOpen(!isOpen);4 return (5 <>6 <button 7 onClick={toggleOpen} 8 aria-expanded={isOpen}9 aria-controls={isOpen ? "panel" : null}>10 Toggle Content11 </button>12 {isOpen && <p id="panel">This is some panel text</p>}13 </>14 )15}
Accordion? Drawer? Toggle? Whatever you want to call it. But here it is. A button that when activated, will show or hide additional content. To make this accessible for all users, we've added two ARIA properties:
Aria-expanded takes a boolean value; when the value is true, it will announced "expanded" to the user, otherwise "collapsed". It should be placed on the element that is controlling another element (a common mistake is placing it on the element that expands/collapses, but if the element is collapsed and not rendered, then it would not be focusable to even hear the state).
Aria-controls takes the ID reference of the controlled element, and it can take multiple IDs delimited by a space. This is more of a progressive enhancement, as many ATs don't support this. The ones that do provide users with a keyboard shortcut to shift programmatic focus to the controlled element (which is pretty handy)! We've also set the value to null when the state is not open to avoid any issues ATs might have with a null ID reference.
With just these two properties, we've managed to make our component more accessible. Yay! Now when users are focused on the button, it will announce "expanded" or "collapsed" depending on the state, and users will understand that this button is hiding/showing other content on the screen (assumedly content that appears immediately after/next to this button).
Programmatic Names
Programmatic names refers to the textual label or content of an element and are useful to users for distinguishing between elements. Names are the alt text we give images, the inner text of an element, and the label
of an input
element. Here are some ways to program an element's name:
1// Inner Text (preferred)2<p>This is a programmatic name</p>3
4// Alt Text for images (preferred)5<img src="some-image.png" alt="This is also a programmatic name" />6
7// Label of input (preferred)8<label for="inputId">This is a programmatic name</label>9<input id="inputId" name="notProgrammaticNameFYI" type="text" />10
11// aria-labelledby12<h2 id="formHeading">This is the Programmatic Name for the Form element</h2>13<form aria-labelledby="formHeading">14 <input id="consentCheckbox" type="checkbox" value="agree" />15 <label>I understand that the aria-labelledby attribute changes the programmatic name of the form element from all this inner text content to just the inner text of the heading element. This content is still reachable if a user moves focus to the children element within the form.</label>16 <button type="submit">Submit</button>17</form>18
19// aria-label20<a href="#" aria-label="This is the programmatic name">This text is ignored</a>21
22// BAD BAD BAD - aria-label - BAD BAD BAD23<a href="#" aria-label="">This text is ignored. As long as an aria-label attribute is present, it WILL override the inner text</a>
I've thrown in some ARIA attribute methods in that example, but as we know by now, those methods aren't as preferable as non-aria methods. It's more common to use those methods when retrofitting inaccessible code.
Images
The most common accessibility issues arise around images. Between not knowing what constitutes appropriate alt text to when to even add alt text, it's easy to get confused about the rules around accessible images. So let's clear this up.
Every image needs an alt tag.
Done. Honestly, that's the bulk of a developer's job: just make sure an alt tag is present. The confusion is around what the tag should read. There are several types of images, and if you can discern the type, you'll have a better idea of what content should fill the tag:
Decorative
Decorative images are images that are used to simply add atmosphere to a design, and most often will be most of the images you encounter. Generally, if you can remove or replace the image, and the page doesn't lose any of the meaning or context it's trying to convey to its user, then it's decorative. Since it's not vital to the context of the page, we want to suppress the image for users using assistive technologies (ATs) so that it doesn't create unnecessary noise. We can do this by providing an empty alt tag to images like so:
1// Image used for background pizazz2<img src="lovely-background.png" alt /> // or alt=""3
4// Image where the alt text would be the same as visible text next to it. 5// In this case, even if you removed the image, a user would still know what the button does.6<button>Search <img src="magnifying-glass.png" alt /></button>
Even though we aren't adding any content to it, the alt tag must be present so that users and ATs know that the image was intentionally suppressed. Otherwise instead of skipping over the image, ATs would focus on the image and either read the file name, or announce "image", either of which would cause the user to feel confused or FOMO.
Meaningful
So you might have guessed that "meaningful" images are the opposite of decorative: they provide context or are supplemental to other content on the page. Meaningful images could be infographics, figures, iconography, images with text, or any other image that if removed, would lessen the user experience.
Let's look at some examples:
1// Infographic example: try to summarize what the image is trying to convey instead of describing the actual image2<img src="map-of-travel-route.png" alt="Route starts in Venice, Italy, continues to Flourence, then Rome, with an optional trip to Sorrento Peninsula." />3
4// Iconography example: once again, write what is trying to be conveyed instead of describing the image5<button><img src="magnifying-glass.png" alt="Search" /></button>6
7// Image with text example: images of text such as logos should have alt text conveying the words in the image8<img src="EF-GAT-logo.png" alt="EF | Go Ahead Tours logo" />9
10// Gallery example: when showing off photography as part of an art gallery, 11// a related figure to the content, or any other reason you might believe that the image 12// is important for understanding or selling context, try to describe the image as best as possible.13<img src="photo-in-art-gallery.png" alt="5 Zebras scattered across the golden fields of Africa with a single tree in the distance under a cloudless blue sky" />
The main thing to remember for developers is that every image element will need an alt tag, and if you can discern the type, you'll know whether you can just apply an empty alt tag, or if you need to make sure that authors can set the text if integrating with a CMS. If you have trouble figuring out the type of an image, this handy alt decision tree should help!
Focus
There are two things to know about focus: it must be visible and sometimes managed.
Achieving a visible focus is easy. In fact, it's already done! You know that outline that appears when you click on a link? That's a visible focus. Sighted keyboard users depend on that outline or some visual indicator to keep track of where they are on a screen. The problem is often when we intentionally remove that indicator using outline: none;
, making navigation nearly impossible for some users. Instead of removing the outline entirely, you can remove it for just mouse clicks! Check out :focus-visible which allows developers to style the focus indicators for mouse vs keyboard activation.
Now let's talk about focus management. Usually, a keyboard user navigates a page sequentially by just hitting the tab key (or shift+tab to go in reverse order), but sometimes we need to hijack their focus to take the user to where they want (or need) to be. A simple example would be skip links: usually an anchor link a user can click to scroll to the appropriate place on the screen. The thing about scrolling...is that it only helps mouse users. To create the same experience for keyboard users, we need to also use something like focus()
to programmatically move focus as well.*
But what about something more complex like a modal? Often we overlay modals front and center of the screen and prevent interaction with anything behind it...but only visually. To replicate that same kind of experience for keyboard users, we need to use Javascript to
place focus somewhere in the modal (it's usually acceptable to place it on the whole modal if it has an appropriate name, the first interactive element within the modal, or the modal title element
prevent keyboard focus from leaving the modal unless the user closes the modal (activate close button or press esc key)
when the modal is closed, return user focus to where they originally were
...which sounds like a lot, but honestly there are tons of library packages that will just do this for us like focus-trap or react-focus-lock.
There are lots of situations where a dev might need to manipulate the focus, and the best way to determine if you need to manage focus would be to simply try navigating solely with a keyboard. If at any point you can't get to an element without knowing exactly where it is on the screen, then you may need to shift focus.
Other Resources
There's sooooooo much. I think this covers some of the larger things? But I'll list some more resources I like to reference. If this is all new to you, honestly don't expect to learn and remember it all in one day. If you start getting in the habit of asking yourself "well what if a user can't do X," you'll start looking up solutions the same as any old problem you encounter. Read blogs, attend webinars, and ask questions!
Notable accessibility vendors (sign up for their newsletters to get related webinars and news)
Deque University - subscription based courses on accessibility (good prep for WAS certification)
Deque Systems Blog - posts written by accessibility experts
TPGi Blog - posts provided by the company that created the JAWS screenreader
Knowbility - non-profit that raises awareness, hosts trainings and events
A11Y Projects
Digital A11y - great resource for understanding WCAG and WAI-Aria (what's available and how to use)
WAI Patterns - examples of how to build certain components accessibly
Understanding WCAG - goes more in depth about each success criteria in WCAG
Follow some awesome accessibility dev experts
Footnotes
* focus()
can only be used on elements that are tabbable (e.g. buttons, links, inputs). To move focus to elements that are not inherently tabbable, try adding tabindex="-1"
, which will allow focus, but not add the element to the tab order. To add elements to the tab order you can use tabindex="0"
(useful for when you have an element within an element that scrolls or something). Avoid setting a tabindex to anything greater than 0, as you begin to manipulate the focus order, which should be determined by the structure of your code.