Video Summary and Transcription
The talk discusses building accessible React components and emphasizes the importance of using the correct HTML elements and ARIA roles for accessibility. It explains how to navigate and select options within a form and how to add supplementary text using Aria described by. The speaker also discusses the benefits of using conditional checkboxes and ARIA disabled to improve the UI. Additionally, the talk explores the role of JavaScript in web accessibility and provides recommendations for testing website accessibility.
1. Introduction and Background
I'm Sid, a front-end developer on the design systems team at GitHub. Today, I want to talk about building accessible React components.
Thank you for the introduction. I'm Sid. My full name is Siddharth but I don't like people butchering my name. I just go by Sid. It's fine. It's easier. I work on the design systems team at GitHub and I spend a lot of time maintaining the React component library that's part of the design system. I want to talk about building accessible React components with a focus on the accessible. A lot of the things I talk about today are actually framework-agnostic. But a fair warning, I'm not an accessibility expert. I'm just a silly React guy. But I did spend a lot of time in the past year working with accessibility experts and I've absorbed a lot of things and maybe as a front-end developer, if you're a front-end developer like me, this kind of an introduction is more helpful than a very accessibility-focused one.
2. Making Accessible Experiences
To make accessible experiences, using the correct HTML element is important. However, there's more to it. Tabs are a good pattern for showing controls in a small space. The implementation involves buttons and changing states. When using a screen reader, the focus on a button is announced, along with its label and associated actions.
All right. Fingers crossed. Let's go. So, things... What are the things that we need to make accessible experiences? If you listen to influencers on Twitter, you should use a button and not use a div. And it's usually accompanied by a code sample with a nice purple background. And so you have to use the correct HTML element. And if you listen to them, that's kind of it. We could just go and have lunch now. But of course that's... This framing is very reductive because there's so much more that goes into it. This is not false. You should still try to use the right element. And if it's an interactive action, then you should use a button. But there's so much more that goes into it, for example.
So there is this interface, there's this popup widget on GitHub where a lot of information is densely packed. So you have code spaces, copilot, and the local tab. And when you have to show a lot of controls in a very tiny space, tabs is a really good pattern and that's what we use here. And it really... I think it works really well if you're using the UI this way. And the implementation is also kind of funky. We have buttons. We do use buttons. And I'm setting a state here of what is the selected tab. Can you see my cursor? Nice. And then we all kind of just agree that when it's a hill like this, that's a tab. And when you change the tab, the hill moves. I don't know why, but we all kind of know this, and this is a design common pattern. So that's what I do. I give a class name. And that's what the class name makes a hill. But what if somebody who cannot see the screen and instead is using a screen reader, what do they hear? What do they perceive? Let's try it out. Also, this is gonna be a test of the audio. So we'll find out. If it's too loud, it will get fixed. Don't worry.
Okay. Let's go. VoiceOver on application. React app. Perfect. So any time you start VoiceOver on Mac, it starts by announcing that VoiceOver has been enabled and it's on the React app. Next. Code. Menu popup button has keyboard focus. So it will announce what element has keyboard focus right now. So if you can't see anything and if you're only hearing this, you know that the focus is on a button. It has a label code. And it has a menu popup. So because you know it has a menu popup, you can press enter and you can open the menu popup.
3. Using ARIA Roles for Accessibility
The use of the correct HTML element is important, but in cases where it's not feasible, the ARIA roles can be used to convey accessibility semantics. By assigning the role 'tab list' to a div and 'tab' to buttons, the semantics are added to indicate their purpose. The element type, such as a button, becomes less relevant, and the focus can still be achieved by setting a tabindex. Panels with content should have the role 'tab panel'.
So let's see what it's going to do. Local button. Cata spaces. Button. Copilot. Button. Copy URL to clipboard. Button. HTTPS. GitHub primer React Git. Content select open with GitHub desktop. Button. Download zip. Button. Local button. Huh. So it circles back to the top. Now, if you're hearing this, I think all you hear is button, button, button, button, and input button and a button. So it's so... We're kind of so used to our design hills that this visually is such a clear pattern, but if you're not a sighted user, if you're a screen reader, none of this makes sense. The hierarchy is just there's a bunch of buttons. Go figure it out. And maybe... I don't know what we did wrong. Maybe we didn't use the right element, right? That's what the influencers want. So maybe we should have just used the tabs element instead of the button element. But of course, there is no tabs element. That's not a real thing. We are stuck with divs and buttons on the web. And this is where the accessible rich internet application spec, or ARIA as it's famously known for, comes in. So ARIA provides a set of roles that convey the accessibility semantics of structures on a page. And specifically, they have a lot of roles already defined. And one of these is tab list. Which is actually what we need. So coming back to this, I can't really use the right element here. But what I can do is use the right role. So if I give my div a role of tab list, and each of my buttons a role tab, now the semantics... I'm kind of adding semantics here to convey to the browser that this is a role tab. And of course, I'm kind of overriding the button and giving it role tab. So does it even matter if it's a button? It kind of doesn't. The only thing that I need to be careful is that if I wanted to get focus, it needs to have a tab index. And then finally, my panels on the end, the one which has content should have role tab panel. So these are the semantics. Now let's try the screen reader again. Menu popup button. Local. Selected. Tab. 1 of 3. Kata spaces.
4. Using ARIA States and Properties
The screen reader announces the selected tab and provides information about the number of tabs available. However, there is a quirk where all tabs are incorrectly announced as selected. To address this, the ARIA states and properties are used. By adding the 'aria-selected' property to the relevant elements, the correct selected tab is announced. Testing confirms that the screen reader now correctly identifies the selected tab and allows for tab switching.
Selected. Tab. 2 of 3. Copilot. Selected. Tab. 3 of 3. There's a lot of things to like here. It's announcing what the focus has. So it said local. And then it said selected. So it knows that this tab is selected. It said tab. So now you know you're in a tab. And it says 1 of 3, 2 of 3. Even if you're on the first one, you already know that there are 3 tabs.
And the fourth thing that I really love is how it pronounces code spaces, like coda spaces. I think we should rebrand. Coda spaces is so much nicer. But there is something quirky that's also happening here where it thinks all the tabs are selected. So even though I know from my hill that local tab is selected, it says copilot selected tab 3 of 3. And that's because, again, screen readers don't really have a way of understanding what's visually selected or what we have all somehow agreed that a hill means a selected tab. So we need to tell it.
And this is where we go back to the spec. And instead of roles, we look at states and properties. So states and properties provide specific information about an element, like its current characteristics. For example, selection. Or its relationship with other elements on the page. So over here, what we should do is add aria-selected. And this tells you can give aria-selected to a bunch of roles. And the spec has a very good relationship map of what roles expect what properties. In this case, aria-selected. And I'm using the same React logic where the same I'm using to decorate my class. I'm using the same one to tell which one of these is selected. Okay. Let's test it out again. Local. Selected. Tab. One of three. Cata spaces. Tab. Two of three. Copilot. Tab. Three of three. Perfect. So now it only thinks one of them is selected. And only if I press enter on any of these tabs, that tab will become selected and the corresponding tab panel will be shown. Perfect.
5. Implementing Arrow Key Navigation within Tab List
To navigate within a tab list, arrow keys can be used instead of tab keys. By resetting the tab index and adding event listeners for arrow key navigation, the user can easily move between tabs within the widget. This functionality is not provided by ARIA roles or properties, so it needs to be implemented manually. A hook called use focus zone is available to handle this common pattern in React applications.
Okay. Let's see. What's next? So I think I want to go and copy the URL. So right now you see my focus is on copilot. If I want to go to copy, that's part of local. So let me go back to local. Cata spaces. Tab. Two of three. Local. Selected. Tab. One of three. Cool. And now that I'm here, I want to go into the tab panel and select the first button. How would I do that? I guess I would just tab again? Cata spaces. Tab. Two of three. Copilot. Select the URL to clipboard. Button.
There's something about that that feels very off. You were on a tab and then you have to go through all the other tabs to enter the tab panel. This feels like something is wrong. This feels like we did use the right elements, we added the right roles, but still something seems off. And that's because if you look at this, there are two distinct roles here. There's the tab list and there's the tab panel. Navigation between them doesn't have to be only through tab keys. In fact, the ARIA spec has a very brief section about it where it says enrich Internet applications, the user uses tab to go through significant complex widgets such as a menu or a spreadsheet or a tab list, and uses the arrow keys to navigate within the widget. So if you're in a significantly complex widget like a tab list, you don't have to tab the widget, you can use arrow keys within it and when you press tab, you exit the tab list and enter the next widget.
So how do we implement that? There is no ARIA role for it, there is no ARIA property, there is no right element. You have to do it yourself. So to take it out of the tab order, I'm going to reset the tab index to minus one, that takes it out of keyboard focus, but I do need some way of entering this widget. So I'm going to keep one of them as tab index zero so that it will keep it in the tab order and everything is minus one. So when you enter the tab list, you enter through the selected tab, you can use the arrow keys and then you can get out. How do we make the arrow keys work? Well, it's good when you add an event listener on key down, and then if event key is arrow right, then focus on the next tab. If it's left, then focus on the previous tab. Inside these functions, focus next tab, it literally looks like you look at the DOM element, you find the next tab, and then you call .focus on it. It's a very old school JS query, iterative kind of code. Just a quick demo. This is how it works. You open the tab, you can use arrow keys to navigate between them, and then you can press tab to go to the next thing. Now I can press shift tab to come up, and now I'm back in the tab list and I can use arrow keys again. This is a common enough pattern that we've actually abstracted this out into a hook, React hook, and we call it use focus zone, and you can give it what keys do you need to bind on. In this case it's a horizontal thing, so we say focus keys.arrowhorizontal. It's nicely typed. That's an enum. And then it returns a ref which you can add to a container. This part, I mean, primary act is open source. If you want to steal our code, please do.
6. Improving Tab Navigation and Notification Settings
Changing tabs on focus instead of click can provide a nicer user experience. While the ARIA spec doesn't mention when to change tabs, it does emphasize updating ARIA selected when the tab changes. The ARIA authoring practice guide (APG) provides useful patterns for tabs and other accessibility practices. It's important to use elements, rules, ARIA properties, and keyboard navigation to make interfaces accessible. An example of changing notification settings on GitHub demonstrates the importance of semantically correct code and screen reader announcements.
If you want to file bugs, don't. Please just fix them. OK, let's see. The last thing here is that instead of on click, what would it be like if we changed the tab on focus? Notice to see that here, as I'm moving through the tabs with my arrow keys, the tab panel also changes. And that's almost like a nicer way, and the ARIA spec actually doesn't even talk about it. It has no mention of when to change the tab. That's left on you, whether it should be an enter, whether it should instantly change. All it says is when the tab changes, make sure you update ARIA selected so the voiceover can announce when a tab is selected.
For this, we need to go to the ARIA authoring practicing guide, or the APG. The APG has a lot of patterns and actually talks about one of these for tabs where it has keyboard interactions and it's almost like a nicer spec to read on. This is like a checklist of all the things to follow. But be aware that while the ARIA spec is a recommendation for browsers, for accessibility software, for authors like you and me, they call the APG an informative resource, which is just like a nicer way of saying it's just a blog with some code examples so nobody has to follow it. Browsers don't have to follow the APG while they do have to follow the ARIA spec. Some of these practices, like it does in our industry, some of these practices start by people trying to improve on the spec and then they just become common hold. Everybody does them. The tabs, and eventually they go back into the spec. The APG has a few of those patterns as well. Things we have learned so far. Use the correct element. That's still true. But also you need to use rules that convey semantics and structure. You need to use ARIA properties and state that add relations and current status. You need to implement keyboard navigation yourself for interfaces to be accessible. And of course, there's more.
So, I have another example here where I'm trying to change my notification settings on GitHub. This is in the settings page. And there is a nice little form where I can select email, and then it gives me an additional option of if I only want to notify for failed workflows, which makes sense. And then I select it. It's really nice because it shows on the button what you have selected. And the code for this is pretty nice. I'm using a button. I'm using a form. I have a field set. I have legend. I have input type checkbox. There's an ID. There's a label. Like everything about this is perfectly semantically done. But let's see what the screen reader announces. Me. Never. Menu popup collapsed. Button. Okay. Again, it's a menu popup. It says notify me never. So we know what the current settings are. And as in the previous example, if a menu popup, I can press enter and open the popup. On GitHub. Tick.
7. Navigating and Selecting Options in a Form
The speaker demonstrates how to select notification channels using accessible checkboxes and provides a step-by-step guide on navigating and selecting options within a form.
Tickbox. Select notification channels. Group. Nice. That's useful. So it says the current focus is on the label called on GitHub. It's an unticked tickbox. So tickbox is a checkbox. And it tells me that it's unticked right now. And it also tells me that this is part of a group called select notification channels. So if I'm not looking at this at all, I still know that where my focus is right now. And I'm in part of a group. So there's more tickboxes for select notification channels. So I can tap to see what my other options are. I know there's on GitHub. Email. Untick. Tickbox. Okay. App. Untick. Tickbox. Okay. Cancel. Button. Save. Button. Cool. So after the three options, I go to save and cancel. So I know there are three options. I like email. So I'm gonna select email. I'm gonna tap back. Cancel app. Email. Untick. Tickbox. Cancel app. Email. Tickbox. Okay. And now that I have selected my option, it's a form. I can press enter and save, which is what I'm gonna do. Change is saved. Notify me. Email. Menu popup collapse. Button. Perfect. I did a lot of things right here and I saved an accessible form.
8. Using Conditional Checkboxes and Aria Disabled
The speaker discusses the use of conditional checkboxes in React, highlights the importance of not hiding important elements from users, and explains the use of Aria disabled to make elements accessible yet disabled. They also mention the need to update the on change event for Aria disabled elements.
But some of you, I see you smiling very sheepishly. Just like, I see you. I caught you. So there is something that we're doing here.
One of the checkboxes only shows up when another channel is selected, because it's a conditional checkbox. You can't have it unless you select something else. And this is just, like, pretty design. If it's false, don't show it. But when it's true, that's when you show it. And I think it's very common in React to do this kind of conditional rendering. But if you're a screen reader user, you wouldn't actually see this pretty animation of, like, I spend so much time in CSS animate this without frame in motion. But it doesn't matter, because it wouldn't get announced. People wouldn't even know it exists. So the first thing we have to do is not hide things from users. That seems logical. So let's just show it always.
But we can't really let people select this. There is a condition on how to... When you can select this. So I'm going to add disabled. Unless something is selected, it's going to be disabled. There's a problem with disabled, though. So if I tab through my interface, disabled actually takes it out of the tab order. So now not only is it hidden, it's, like, forever hidden. You cannot even access it. So disabled is bad. It was news to me. I didn't think disabled. The right way to do this is a thing called Aria disabled. And Aria disabled is interesting, because it still gets you keyboard focus. You can still style it yourself. So I'm here adding my own disabled styles, when it's Aria disabled. And now... I can access it.
Only notify for failed workflows. Dimmed unticked. Tick box. So not only is it in the tab order, it also announces dimmed unticked tick box. And dimmed is like VoiceOver's way of saying it's disabled. I don't know why it doesn't say disabled. It says dimmed. But it's fine. VoiceOver users know that dimmed means this. So that's a good thing. And then the surprising thing that you have to take care of yourself is also update your on change. Because Aria disabled is not the same as disabled. You can still register events on it. So you have to catch those events and add your checks to that. All right. The final nice thing I'm going to do is I'm gonna add a description of why this is disabled.
9. Adding Supplementary Text and Demo
The speaker explains how to add supplementary text using Aria described by. They discuss the option to show or hide descriptions based on user preferences and emphasize that website authors do not have control over this feature. The speaker then provides a quick demo of the entire process, highlighting the importance of selecting at least one channel for the 'Only notify for failed workflows' option.
And I can add this extra supplementary text by attaching it with Aria described by. So on this input, I have a label, which is connected by IDN4. And I have a description, which is only sometimes shown. So this is how it looks. And... Only notify for failed workflows. Requires at least one channel to be selected. Dimmed unticked. Tick box. So it also tells you why it's disabled. And I'm using Aria described by instead of putting in the label. Because it can, like, as you can see, this is a lot of text. And it can become lengthy. So some screen reader users prefer not to have descriptions. And that's the setting that you have. You can only show labels or you can show labels and descriptions. As website authors, we do not get that control. The screen reader user gets that control. Which I think is pretty neat.
So after doing all this, let's do a quick demo of how this is from top to end. Define me. Never. Menu popup collapse. Button. On GitHub. Untick. Tick box. Select notification channels. I have one option. I know it's on GitHub. Email. Untick. Tick box. App. Untick. Tick box. Only notify for failed workflows. Requires at least one channel to be selected. Dimmed untick. Tick box. Cancel. Button. Cool. So I know there are four. And the fourth one is has a condition on it. It says requires at least one channel. So I'm gonna go back up, select at least one channel, which is email. And then I'm also going to select failed workflows. Only know app.
10. Using Conditional Descriptions and ARIA Disabled
The speaker discusses the benefits of using conditional descriptions and ARIA disabled to improve the UI for both visually impaired and sighted users. They highlight the clarity and functionality of the design, which provides clear announcements and explanations for disabled elements.
Only know app. Email. Untick. Tick box. Tick. Email. Tick box. App. Only notify for failed workflows. Untick. Tick box. Tick. Only notify for failed workflows. Tick box. Change is saved. Notify me. Email. Failed workflows only. Perfect. So now I have everything announced. It's very clear. It also announces when it's ticked. The description can be conditional. So it goes away when it's not necessary anymore. And personally, I think this is actually a better design for sighted users as well. It's more clear. It actually tells you why something is disabled. And you can still have that tiny animation of when you don't need the error. So just me, but I think it's actually we made the UI better by doing this.
11. Designing with Accessibility in Mind
The speaker discusses the importance of designing with accessibility in mind and the use of ARIA disabled to disable elements. They mention the limitations of using buttons for tabs and how it can override styles and the tab order.
Cool. Going back to the list. All of the four things are true. But also, we have to design with accessibility in mind. There are no roles, there are no elements. While designing, we have to think about this.
And finally, be careful when disabling elements. If you can, use ARIA disabled. Of course, there's more. But the ticker is the very scary thing. So I'm gonna stop here.
These are the six things that, if you want a photo, this is the slide to take a photo. And all the... This link actually doesn't work. Because I made my slides and I put this and I forgot to put anything there. So check this after lunch. And all the links that I talked about would be here. And if you want to see some garbage, follow me on Twitter. That's enough from both of us. Screwed. Voice over off.
Thank you. Someone in the audience, they did say... What software did you use for your slide? It looks amazing. This is a keynote for Mac. But the left thing is just a text area with syntax. And the right is videos. Like a bunch of videos that I recorded. Because I'm too scared of doing this demo live. Fair enough. Live demos are always intimidating.
We'll come back to the second half of this question in a bit. But let's start with the top, the first question that came in. And folks who are walking in and out, thank you so much for keeping your voices down, just so that the ones who are still here can hear the question. So this is one of the things you kind of covered. Because for me, especially when it comes to accessibility, I'm thinking... Okay. Be semantic. And that will have lots of downstream effects. And when you took it away from button and switched it with Tim, I was like... That's sending off alarm bells. And this is kind of the question. Isn't it better to still use the closest semantic element for tabs? Like still a button. Because that could handle focus. It would be keyboard interactive. I will say this question came in before you addressed the keyboard interactivity and stuff. So the truth here is that... It's like this much better. Because if you're using buttons for tabs, you're going to override all of the styles. You're going to override the tab order.
12. The Importance of Semantic Elements
The speaker discusses the importance of starting with a more semantic element, even though it may be overridden later. They mention the possibility of missing important states when using non-semantic elements and highlight that there is ultimately no difference in the end.
Because you don't always want it focusable. There is a condition on focusable. So you're kind of taking away almost all semantics from it. And at that point, it kind of makes no difference if that was a button or a div or a span. Because you're overriding everything anyway. But I would say... It's nicer to still try to start with something that feels more semantic. Because as you're overriding, maybe you miss something. Like, for example, I would have missed adding a disabled state to the tab. But my buttons would still have a disabled state. So it depends. It's nicer. But eventually you will see that there is no point. There is no difference.
13. The Role of JavaScript in Web Accessibility
The speaker discusses the use of JavaScript for adding accessibility to components. They explain that while some basic accessibility features are already baked into browsers, JavaScript is necessary for customizing keyboard navigation and creating dynamic widgets. The speaker highlights the importance of carefully considering which JavaScript functions are necessary immediately versus which can be deferred. They mention the Primer React component library as an example of the decision-making process. Ultimately, JavaScript is required for web accessibility.
All right. I'm going to keep a running counter of how many it depends. We're at one. We're at one. All right. Let's go with the next question. Which is asking about how you would do this without JavaScript. I know sometimes people try to avoid using JavaScript to do all of this. And a lot of times, you had the basic... The actual elements. But then you had to build in this accessibility manually with JavaScript. Before you removed it and brought in the primary... I was like, this is getting quite heavy for a component. But anything you suggest?
Yeah. So anything that's already baked into the browsers. For example, IR roles, states. All of that would work without JavaScript. But for keyboard navigation, the default is tab. So if you want anything else, if you're creating a widget like a tab list or a menu, you do need JavaScript. It's a tricky thing. Because you almost want to make sure that whenever the UI renders, there is already enough JavaScript there so that the UI can be accessible. You can start using it. And it's almost like a... I know nothing about performance here. But there's a lot of thinking that goes into it about what JavaScript is necessary immediately and what JavaScript can you defer. And a lot of this we bake into our components. So Primer React is a component library which has a tab, and there's a bunch of components. And we're constantly asking these questions about what JavaScript is required to bake in right immediately and what is something that's decorational or can happen later. So unfortunately, there is no answer. You do need JavaScript on the web.
14. Testing Website Accessibility and Public Speaking
The speaker discusses tools for testing website accessibility, recommending the Axe family of tools. They highlight the limitations of automated testing and emphasize the importance of manual testing. The speaker also addresses the challenges of disabled states in browsers, expressing a desire for more advanced accessibility controls. Lastly, they share their approach to public speaking, which involves treating the audience as friends and focusing on improving their content with each presentation.
Fair enough. Yeah, absolutely. We'll come to this one because we'll leave it to the last one.
Another person, people like me, for example, sometimes I think, oh, my site's accessible, but then maybe there are some unique cases that I didn't know. Are there any recommendations for tools to help test your... To assess how accessible your website is?
Yeah. I think so. There's a bunch of tools, if you start looking for them. The one I really like is the Axe, A-X-E family of tools. And they make a lot of plugins. They have a CLI command, they have a jest plugin, they have a storybook plugin. So in a lot of environments, if you're doing something wrong, it starts to show you. So for example, if you're using a role tab without wrapping it in the role tab list, it will catch that and it will warn you about those. But then there are things like the design one, where we disable something that makes it inaccessible. There is no tool to catch that. That's a very human... You have to test it yourself. So I would say there was a study which looked at how many accessibility bugs can be caught by tools. And I want to say it came around 40% or something. It's very low. So there is no... I would say it gets you really far, but there is no substitute for manual testing. For sure.
And because you spoke about how the disabled thing wouldn't necessarily have been picked up by a test, that's another question, which is like if disabled states are always inaccessible, is that something that maybe the browser should be working on to change that behavior built in?
Yeah, that's the hard part with all of this. I would say accessibility controls are still not as mature as browsers. So there is a huge gap. So for example, everything I showed you works on voiceover, on Safari. But if I was using Windows and NVDA, the announcement would look slightly different, the gotchas could be slightly different. So we're still in a IE6 rounded corner, kind of place with accessibility tools. But I wish browsers could do more, but if you follow the updates that do happen in browsers, it's very tricky because once they've built disabled, there is no way they can change it because a lot of websites actually depend on disabled for genuinely disabling something intentionally from everyone. So it's always like, now it's there, now we need a new disabled state, we need disabled V2 which can actually solve for this. So I wish they could, but I don't see... A petition for disabled V2 in the browser.
And last but not least, but I really want to know the answers to this, Sid, your speaking style is so captivating. How did you go about perfecting your speaking skills? I need to copy some of them.
I don't know. That's a hard question. I don't know what I'm doing. I'm so nervous that I'm just looking at my screen and talking. I think one thing that maybe works out for me is that every talk of mine seems like I'm just pair programming. So I'm pair programming with a friend. And then it's no longer that this is an audience that has come to listen to me because that's scary. This is just me showing you my shitty code and hopefully making it better with every slide. So to become a great speaker, have friends. I'm going to struggle.
Thank you so much, Sid. Let's give it up for him one more time.
Comments