But…why??
A look at common patterns found on the web, how they are harmful for accessibility, and what to do instead.
A presentation at EmberFest in September 2024 in Dublin, Ireland by Melanie Sumner
A look at common patterns found on the web, how they are harmful for accessibility, and what to do instead.
Anyway, I want to take a minute and talk about why this talk.
I’m the product accessibility lead at HashiCorp, and I work on the Design Systems Team. You may have heard of our design system, Helios. I love it. It uses semantic markup, it has lovely design, it meets accessibility compliance requirements, we have tests and really thorough documentation.
It’s been a dream to work on.
But as y’all know, I’ve been talking with our community for a long time about accessibility, and that a design system alone does not make your application accessible.
But what really solidified this talk as more than just a saying or concept, was that while working to make our design system components REALLY accessible… And really tested… And really documented….
I noticed that I kept seeing similar accessibility issues in the applications using our design system, so… I started taking some notes.
And then those notes started taking more shape. And then Marco said, “please talk at EmberFest” So I said yes, because I knew what I wanted to talk about. Plus Dublin, so obviously.
And that brings us to my most predictable slide, the agenda.
But first, some disclaimers.
The premise of this talk is that we’ll look at patterns in real life and that means real code. I don’t show you these things to shame any designer or developer, because we’ve all written code and later looked back and thought “oh dear lord what have I done” I did that just this morning even.
I show real patterns and real code I want to guide us to do better, which means looking at real stuff. That way we can do better, next time.
I’ve said it before and I will say it again, accessibility is a journey. It’s about progress, not perfection.
First up: how do you make unique, accessible names?
In this UI example, we see something that is very common in technical apps today: the ability to add integrations with different products, or maybe login with different platforms.
We see these cards, each with the company’s logo, the name of the company that could be potentially integrated with, and the link that will take you to the page where the user can perform that action.
Where this breaks for accessibility is that all of these links have the same name, even though they go to different URLs.
Now, real quick: The mechanism for screen readers that allows users to navigate through a site by groups of elements is called different things in different screen readers. I’m going to call it a rotor, because in VoiceOver on Macs, that’s what it’s called. Just know that it is probably called different things by different screen readers.
In this case, if the user is navigating by rotor, they will see a list of links, all named “add”. Add…what? There’s no way to differentiate these links from each other in the same quick way that a sighted user can.
Even if the user navigates with the just the TAB key or the keyboard shortcut to go to the next link, going from one interactive element to the next, they will still hear “Add, link” over and over and over again.
That’s…not useful.
So before we look at some options, let’s take a quick look at what this code might look like. This code sample is heavily edited so we can focus on the important bits.
Let’s look at the option using the aria-label attribute.
In this approach, we add the aria-label attribute to the link, concatenating the word “Add” along with the Integration name. When rendered, we’ll still have the same element(s) in the browser.
And like the aria-labelledby option, the screen reader will tell us “Add GitHub Actions, Link”
So let’s see what it looks like to use the aria-labelledby attribute to fix this problem.
We’ll start by adding an id attribute to the div with the integration name Then we’ll add an id to the span with the word “Add” in it. Then on the link, we’ll add the aria-labelledby attribute with the two id attributes we want to use, in the order we want them to be read to the user. When rendered, we’ll still have the same element(s) in the browser.
And we’ll get the same result, the screen reader will tell us “Add GitHub Actions, Link”
So if we compare the rotor list we saw before With a rotor that has a list of links with accessible names We can immediately see where the improvement is. The user can now differentiate quickly and easily. Which one would you prefer?
Here’s a little bonus example, an accordion item using the aria-labelledby attribute. By adding an id attribute to the h2 element that gives the accordion item a heading, (click) we can then use that id attribute value for the aria-labelledby attribute on the button element.
Furthermore, because we have the aria-expanded attribute here, the user will be informed if the element is expanded or collapsed. So we get that information for free.
Ok. I did warn you there might be a detour, and guess what, it’s time for a detour.
A detour into the accessible name and description computation specification. Wow that’s a mouthful. I just call it AccName. You can too if you want. It’s easier and people will know what you mean.
This detour is happening because I’ve had too many developers tell me that they don’t understand AccName and I’m the co-editor. So I feel responsible for that.
So I’m going to take a few minutes and explain a few things because it’s worth it to understand what’s going on here.
So I’m going to explain this to y’all from a few angles. That’s why we have a mindful detour agenda. First, some context Then we’ll look at the accname instructions for the browser Then we’ll talk about some ways browsers are like, ”yeah but no” Then we’ll look at the guidance for developers Then the accessible description instructions for the browsers And finally the developer guidance for descriptions.
So. Three things to know about this spec.
First, it provides instructions to browsers for how to calculate the accessible name and the accessible description. Both may exist. Neither may exist. Browsers need to figure that out,
Because the browser’s goal is to return the results to the accessibility object.
Now, they will follow the specification FOR THE MOST PART. But, they may also do other things if they are convinced they need to do that.
Browsers are supposed to return the first thing they find, looking in this order: Look for an aria-labelledby attribute Then look for an aria-label attribute with a non-empty value Then look for an aria-describedby attribute Look for semantic content (text of a button element. Label. Legend.) Finally, if there’s nothing else, look for title or placeholder attribute.
There are some edge case exceptions to know about, so you don’t run into them and then are like “WTF MEL YOU TOLD ME THE WRONG THING”
First, we previously noted that they must return the first thing they find…even if that thing has an “empty” string. And this really makes like, a space between your quotes. Not even joking. It’s because there’s been a lot of back-and-forth to explicitly define how whitespace should be handled in the AccName spec, and browser implementers kind of have to agree. So there are some realllllllly weird edges to this one. Just make sure that you’re not putting any whitespace in your “empty” strings.
Second, they can choose to expose text even if you’ve used the aria-hidden attribute on it. This is because they feel it’s better to return something to the user with AT, rather than nothing. So “thar be dragons.”
Finally, browsers implement exceptions as they see fit. There is no specification for implementing exceptions. So…just…idk.
The thing is, if you aren’t doing weird stuff then you’ll probably be fine. It’s when developers try to get clever that these issues crop up.
Semantic content should be your default go-to. The first rule of aria is to not use aria if you can help it. It really is the best and the most reliable thing to do. If a suitable element that can be used to provide the label content: use the aria-labelledby If no suitable label already otherwise exists: use the aria-label attribute Don’t use the placeholder or title attributes for an accessible name. They just weren’t implemented appropriately and are terrible. So just don’t do it. Placeholders, if used at all, should be used to demonstrate to the user the formatting for the data you’re asking them for. That’s it. Maybe. You’ll read online that aria-label is not translated. This is referring to sites that rely on google translate in the browser to translate the page. But you’re Ember developers and you use i18n and all, yeah? So you don’t have to worry about it.
AccDesc: browser instructions Look for an aria-describedby attribute (if not used for AccName) Then look for an aria-description attribute Then look for elements that are eligible for the description calculation, and not already used for the accessible name computation: table caption text content summary element, text equivalent computation of subtree The title attribute value (again, if not already used)
AccDesc: dev guidance Semantic content (i.e., Table caption element content) Use the aria-describedby attribute for help and error text Takes an id as the value It can have multiple values (space separated id attributes) The aria-description attribute is also a possibility It should have a string value It is preferred to put descriptive text in the DOM, though.
Loosely, you can think of aria-describedby as the description equivalent of aria-labelledby And aria-description as the description equivalent of aria-label
And that is the end of this detour. If you have specific questions or want to know more about things like why it’s so hard to define whitespace rules in the accname spec, come talk to me later.
Ok, on to the next. Let’s look at Headings, Labels, and Instructions. I grouped it this way because there is WCAG guidance about Headings and labels And also Labels and instructions And in my experience, all of these things end up being related to each other. So here we are.
First up, let’s talk headings.
Here’s your headings cheat sheet HTML heading elements for semantic meaning, CSS for styling Every section should have a heading Headings need to be in order
Browsers will change, and over time, expand to support MORE things But these rules will always apply no matter what
Let’s look at an example.
If you have, let’s say, a modal And you give that modal a title And that modal’s title looks like a heading…
But, that modal title that looks like heading but doesn’t sound like a heading to a user with a screen reader
Then it’s not a heading, it’s an accessibility failure.
This matters because the heading tells the user what that part of the page is about.
This matters because users with screen readers can navigate to the next heading with a keypress.
This matters because users with screen readers can navigate through a list of all headings
This matters because it provides users with the means to quickly navigate to the part of the page they care about. Or get an overall idea of what the different sections of the page are.
Like you can do. All at once. With your eyes.
So, what to do instead?
Turn it into a header if it’s acting like a header. Let’s think about that modal. A modal, when implemented correctly, will use the dialog element and mark the rest of the page as inert, or unusable.
Now, If we think about this from the perspective of the user with AT Either the page “exists” or the modal “Exists” But both do not exist at the same time.
If we pair this concept with the “have a single h1 per page” concept Then we can see that modals should have an h1 as their title heading element.
Next up: labels.
Here’s your label cheat sheet.
Labels must exist The markup must be valid and the label must be correctly associated with the input The label must also be informative and the user must be able to discern what is required of them for that input. Finally, the visible name of the label must also be in the accessible name of the label. This is for users who use speech control software.
That’s FIVE different ways labels could fail for accessibility. FIVE. For ONE THING. This is why I frequently tell folks to not get all bendy with the code they put on the web.
So let’s look at a couple of label examples and see what’s what. First up, we have a lovely application name label here. Let’s peek at the rendered code. (click) Ok, let’s apply the cheat sheet: Exist, Correct, Clear, Visible.
Let’s look at this other example. Here we have an input with the label of “Name” Let’s peek at the rendered code. (click) Again, let’s apply the cheat sheet: Exist: Does a label exist? (click). Yes Correct: Is is associated correctly with the input? (click) yes, we see the label/for and input/id Clear: Is it informative enough for the user? Hmm. Name. First name? Last name? Full name? What does name mean here? (click) This would fail, because it’s not informative enough. Visible: Is the visible label in the accessible name? (click) Yes, the visible label is in the accessible name, so it would pass that criterion.
One more example. Here we have an input asking for your cat’s name Peeking at the code (click) We see…oh! We see an aria-label here. Perhaps the developer wanted to give the screen reader user extra information. Perhaps they were trying to be helpful. When we turn on Voiceover (click) we see that the accessible name of the input is “what do you call your cat?” So let’s see how this stacks up in our little checklist.
Exists: (click) yup, the label exists for the input field Correct? (click) yup, the label is correctly associated with the input Clear? (click) yes, the label is clear…in both cases. Visible (label in accname)? (click) now here’s where it fails. The visible label is not in the accessible name.
So fixing our labels, let’s look at a corrected example of the Application Name labeled input.
(click) we’ve added the for attribute to the label element and the id attribute to the input element.
How does it stack up?
Exists? (click) yuppers! Correct (click) definitely now correctly associated! Clear? (click) well that passed before, so it isn’t going to fail now. The label is clear enough. Visible name in accName? (click) you betcha!
And I’ll tell you this for free, you can still give a little extra hint to users with screen readers.
In the cat’s name example, it was incorrect because the visible label wasn’t in the accessible name.
However, if we change aria-label to aria-description, we get (click through) Green lights all the way!
Don’t do this for all help text, but you can do it if you want to add a little something extra for screen reader users.
Ok now to the third part of this. Instructions.
Let’s think about instructions in terms of help text. Now let’s look at this modal. How many accessibility issues do you see just by looking at it? (pause) There are at least four that are visible from this screenshot. Since we already talked about headings, let’s look at the other three.
First, we have the word “DELETE” in all uppercase - this is problematic because not all screen readers treat uppercase letters the way they are supposed to. On top of that, voice engines (the voices used in screen readers) have varying levels of support to read uppercase letters correctly.
Second, we have the word “below.” This is referred to as a sensory characteristic. Except, the user with a screen reader hears this when they are focused on the input field, so what does “below” mean? It only has meaning for the sighted user.
Third, we have a disabled button, which means that the user with a screen reader may not even know that the button is even there. In VoiceOver, it will say “button, dimmed” which is the hint to the user that a disabled button exists, but in general, if an interactive element has the disabled attribute, then it’s completely hidden from screenreaders.
So because we have delete in all uppercase And the delete button isn’t active until we get it right Only, the user may not hear that it’s in uppercase (a common setting is to announce the word in a higher pitch, like (say it in a higher pitch) DELETE) So the users types in delete in lowercase and still nothing happens. They are blocked from continuing and have no guidance here as to why.
So let’s say that our designer is super busy and we want to make this more accessible and not drastically change the design because we love our designers and we want to keep our happy relationship. What do we do?
We’re going to change the help text in two ways. First, we’re going to put parentheses in with the “all caps” so that a user with a screen reader will definitely know that uppercase DELETE is required. Next, we’re removing the word “below” because if you’re focused on the input when you get those instructions, there is no “below”…and if you can see the screen, there’s only one input there so in addition to a violation of the WCAG Success Criterion Sensory Characteristics Finally, we’re updating the button text to “Confirm Delete” and we’re not going to disable the button; The user will get an error message if they do not fill in the field properly, and that’s not only completely acceptable, it’s ideal. Our form’s validation can make sure that absolutely nothing happens to the user’s data unless they fill out the form correctly, and ALL users will be able to figure out what is expected from them.
Next section is reflow, resize, space
And by this I mean three WCAG requirements that are all related: Reflow Text resizing Text spacing
Now your designer might say, “we’re a desktop app. We don’t “support” mobile.”
and then they give you a bunch of designs with fixed pixel widths everywhere, which is Figma’s fault, but ya know what?
It’s only mostly Figma’s fault. There’s math for responsive design, and developers are more than capable of doing math. Or maths if you prefer. So I don’t really accept the excuse.
target ÷ context = result
It’s 2024 and you don’t “support” mobile? Ya sure about that?
But fine, time is money and money is constraints.
Let’s think about this in terms of desired outcomes, though.
What do we want the user to be able to do?
They should be able to zoom up to 400% They should be able to increase the font size up to double They should also be able to increase all facets of spacing for text.
Ok so let’s look at an example of a reflow failure. Here we have a banner on a website, I’ve blurred the unimportant stuff here. This is at full width, where things are as they should be.
But then once we zoom in, we see that the content has not reflowed…it’s starting to become clipped and unreadable. This was not even at full zoom. At full zoom, the content would not be visible. The container is no longer meeting the sizing needs of the content inside.
When done correctly, none of the content is hidden or clipped when browser zoom is used.
The content is still visible and fits the viewport, and it does not require the user to scroll in two directions to see content. Or not see the content at all because overflow was hidden.
This also mostly applies to text resizing as well; while users can especially only just resize their text, using browser zoom has become commonplace enough for that to be an acceptable check in most cases.
For text spacing, let’s look at a before and after first.
Here is some website content before the text spacing requirement has been applied.
And here it is after the text-spacing requirements have been applied.
So let’s look and see what those are.
Since these all should support very specific amounts, have a CSS class you can use for testing, or a bookmarklet and try it out in the browser. https://www.w3.org/WAI/WCAG22/Understanding/text-spacing
To successfully do this, you’ll want to use all of the lovely techniques we have for responsive design.
Relational units Unitless line heights. Grid and Flexbox CSS
And make sure you test!
TBH In the end, “browser zoom support” ends up looking a whole lot like “mobile viewport support”
The takeaway here is that if you meet the accessibility success criteria for reflow, text resizing and text spacing, you sorta get some responsive design for free.
Okay, let’s put all of these things together and wrap this up.
The WCAG success criteria are the bare minimum of requirements for people with disabilities to use the internet. There is so much more beyond that to make sure we’re providing an equivalent experience.
I want you to think beyond. What are we really trying to do here? Think about what a delightful user experience is for all types of users. Think about how you could do more. Maybe the next level for you is internal requirements.
“How can we go beyond, Mel?” I’m delighted you asked. Here’s some ideas. Outline the bare minimum WCAG requirements internally, and then explain ways that your products will do more. Outline requirements for design. For code. For how you will test. For how you will integrate accessibility reviews into your product development and release process.
The thing is, I am calling on all developers to be a little more selfish. Did you know that there’s such thing has an “age burst” and the first one happens (or will happen) to you in your mid 40’s?
Play a video game one day? Have tennis elbow for a month. Hear a sudden noise and turn your head too quickly? You just have a sore neck that’s your life now. Then one day you need reading glasses. Then six months later, you need stronger reading glasses. And your doctor says that your eyes will settle down but you don’t know if you believe that because a year ago you had 20/20 vision. Everything just kinda…hurts now.
So be a little selfish.
Try out your front-end with your keyboard. Check the color contrast with an automated tool that does the math for you. Use browser zoom and make sure it works, and give your eyes a break.
You’ll improve your apps a little bit for accessibility…and your future self.
You might be saying, Mel I won’t be around to use these apps in the future, I’m sure they’ll be re-written by then anyway.
But every single developer influences the way that code for the web will be written in the future. What are you teaching those who come after you?
Finally, I’ll just say this.
You do not require permission to create accessible code. It doesn’t have to be explicitly stated in the requirements, you don’t have to be specifically told to do it. You don’t require permission. Create accessible code.
As the Irish say, cop on.