Enterprise Web App Accessibility (feat. React)

Fixing a Link Dropbox Component Exercise

Marcy Sutton Todd

Marcy Sutton Todd

Principle Studios
Enterprise Web App Accessibility (feat. React)

Check out a free preview of the full Enterprise Web App Accessibility (feat. React) course

The "Fixing a Link Dropbox Component Exercise" Lesson is part of the full, Enterprise Web App Accessibility (feat. React) course featured in this preview video. Here's what you'd learn in this lesson:

Marcy introduces a component called "portal switcher" and explains that it has accessibility issues. The instructor demonstrates how to inspect the component using DevTools and identifies the accessibility issues, such as missing alt text and improper use of HTML elements. The instructor then proceeds to fix the issues by adding appropriate attributes and event handlers to make the component more accessible. The lesson also mentions the use of TypeScript and the benefits of using it for accessibility.


Transcript from the "Fixing a Link Dropbox Component Exercise" Lesson

>> We have a component to fix. This is based on something that I saw in the real world of a portal switcher to take you from two different parts of a giant web application. It's cute, it's got this little insurance logo with umbrella, they look sophisticated, and no one knew that there was accessibility issues.

This has been on a site for a while, or a similar thing. And so for our exercise, we're gonna look into this widget and see how many issues we can find. There is a version, so when you hover over it, you can get to the other portal. There's an insurance agent portal and a broker portal.

Different roles that these customers have or users have. So there's a version in this Next.js repository, and there's a completed file in there, or at least a more complete file. There's also a version of it on StackBlitz. If you wanna play with it over there, it saves you from even having to clone this repository.

So I am going to go and find it in our project directory to make some changes. But let's do our little investigation here. We'll do it together. So, yeah, so we can keep things moving. But the first thing I'm gonna do is try to tap into it. So I can reach this logo.

It's got a focus outline, but I can't get to the portal switcher. I skipped right by it. And why is that? So I go in here and I Ctrl click and Inspect. I'm gonna go look at it in the dev tools. Zoom in a little bit. So we've got div, div, div, anchor.

That's what I could focus on. That anchor element, if I look at it in the accessibility inspector, it doesn't have any text in it. And if I look at why, is missing alt text. So that's a bummer. That means that that link won't have any, I have no idea what that link does in a screen reader.

And then when it comes to the portal switcher stuff, there's just divs on divs on divs. There's some SVG in here. If I open up the portal switcher, it injects another div. So there's nothing for me to reach and operate here. It's using some mouse events to show it.

And the real component was just like this. It had a few more links in it, but I couldn't get to it with the keyboard at all. So let's fix that. Let's see how many issues we can find with this thing. We're gonna fix this component. We were looking at what goes into accessible user interfaces, and we have one for an insurance agent portal that has a little drop-down.

We saw some of the issues with it. Let's go ahead and look at a more complete version. I wouldn't say it's perfect, but it's got a start. So, in this version that's embedded right in our page, I can tab onto that link for the insurance agent portal. That was a div with a click event on it before.

I can also get onto a button for this Change Portal part of the drop down. That wasn't there before. So we'll go to compare the code in a second. But I can hit the Enter key now to toggle this portal, and it's basically showing and hiding this neighboring link on the keyboard.

Whereas before I could only reach it with the mouse. I couldn't even get to this link, unless I was a keyboard user. So this now has the ability to toggle with the Enter key and the Escape key. And I skip by that link when it's not active. So let's go look at what that code looks like.

In VS Code, here's the original portal switcher. So it's a React component, it has some state to track whether it's showing the portal or not. It's got a div that wraps it, and a div just inside of that wrapper element that has onMouseEnter and onMouseLeave that set that portal state.

And that was the only way to get into that component before, which is why keyboard users, it's like too bad, so sad, can't get into it. So I had to add an affordance is what we call that button that I made that you could toggle. We'll look at what that looks like when we go to the more complete component.

So this also had a link. Wrapping the logo, it was missing an alt attribute probably with, insurance company, the name of the thing. What is that logo? Whose company is that? That'd be helpful especially because it's inside of a link so this text will become the accessible name of that link.

And as far as the other widgets on here, there's a div with an onClick, it's like, well, this is kind of suspect, this div onClick equals window.location.href. It's actually pretty easy to delete all that and say a href instead, close out that link. It simplifies, I can actually make it accessible by removing code and just swapping out that div element for an anchor.

And that's pretty easy. It's gonna have a default focus style in this example. You have to watch out for CSS that suppresses focus outlines. The real world version of this. There's a style sheet on this page that just turns off focus outlines for everything and so I had to add a focus style back in so be aware of that.

This Change Portal text that's just kind of hanging out here in a div by itself with an icon. And then there's another div with an onClick down here. This one is another instance that could be just a plain href instead of a div with a click event on it.

So we could change that. So those two are improved. There's probably some more semantic things that we could do in addition to we need something to tab onto. I don't wanna use the links that are there because those are gonna navigate to pages. I need something extra that I can tab onto and toggle.

So an approach for this is to wrap something in a button. That chevron, that was the thing that visually you could say, there's a drop down here. So that's a pretty good candidate for wrapping in a button or something. So this iteration of the solution has that Change Portal text and the icon are wrapped in a button.

And so that button has an aria-label on it. It's maybe not super necessary because there is text on here that says Change Portal. Sorry, my auto-complete. So Change Portal is probably find text for this button. If the button was just around the icon, then the RA label could be nice, if it was instead of wrapping the text just the icon, then toggle portal or something could be helpful.

But there's text on screen, we might as well use that. So here's where we can make this more accessible, is now we have this onKeyDown. That's the button's reachable, and now we have an event that we're paying attention to. I could potentially use a click event here, but I don't wanna do it for every click.

I just want it to work on only certain keys. So let's look at this toggle handler. It's an event handler. And if I pass in the event object as a parameter argument, I can inspect what key the user pressed. So event, I can use the code property to check which key was it, and there's strings for all the different keys.

Enter, space, arrow right, arrow left, escape, all of those things. And I can do things like prevent default if that button is in a form or it's scrolling the page when I hit space bar. You can kind of prevent some of that stuff with event.preventdefault and then I can change the state.

So that button when I click it, if I'm pressing the right keys, I can update that state variable. And then also, to make it a bit more ergonomic, I added an onKeyDown on the wrapper element where the mouse events were. So anytime you're within this widget, if you hit the Escape key, whether you're on the original toggle button or you're on that broker portal link that's the one that shows.

Anywhere within this widget, if I hit Escape, I can close it. So I kind of expanded my event area to be the wrapper element. And that's kind of an ergonomic thing. So that if I'm anywhere within this component, the Escape key will apply. So for that, I match on event.code is equal Escape, and if the portal is showing that state variable, then I'm gonna set that state to false.

And I can handle focus. So if I am on that child blink, I'll show you what I mean. If I open the portal and I'm down here on broker portal and I hit Escape, I wanna send focus back to that button that will be visible. Cuz otherwise, my focus will just kinda go, poof.

It'll get lost. Cuz that element is not rendered when the state variable doesn't match in React. Let's see what that looks like. So I'm gonna inspect. So we don't actually see the broker portal, like React and JSX are injecting it on demand. When that state variable matches, it's not just this version anyway, is not just showing and hiding it with CSS, it's actually inserting and removing it from the DOM.

So if we remove DOM that we're focused on, whoops, now our focus, who knows where it's gonna go. In Chrome, it'll go nearby. In Firefox, it'll probably kick you all the way back up to the top of the page. So we're doing some focus management by sending focus back somewhere that's reliable when it deletes that node.

So we could see it go in and out. So this code, toggleButton, that is using the useRef API in React, which is your new best friend. If you didn't already know, it's amazing for accessibility, it gives you the ability to put refs. So here's this ref toggleButton. So I have a reference.

And I can use that as a kind of a doorway to the underlying DOM APIs. So React uses SyntheticEvent, which is like, if you ever worked with jQuery and struggled to understand whether you had a jQuery object or a DOM node, SyntheticEvent is kind of similar in that it's like wrapping your elements.

And you aren't on the raw DOM node unless you're specific. So the ref API, you can get access to that raw DOM node with ref.current. And you can have collections of refs, they could be an arrays. Ref is really powerful. You can use it for other things too.

It's a great utility. But when I have a ref, I can send focus. So this focus is actually a DOM API method. And if that element's present, I can send focus to it. So this is kind of a pattern, at least in React, of how you manage focus with React elements, kind of in combination with DOM APIs.

So yeah, there's kind of some fundamentals in here that are helpful, like alt text, links that actually work as links. Buttons that we make sure have labels in them. And then moving focus around. I think one area that this could go even further is looking at the ARIA on it.

It's pretty basic right now, just showing and hiding an element, kind of sending focus around. We could potentially look at whether it needs a dialogue roll or some more ARIA on here. We're gonna tackle ARIA in the next section. So this was kind of the start of some keyboard focus management.

But really, we're kind of looking at every accessibility issue we can find within a component. That takes a few passes with tools like the axe tooling. I really like Storybook for working with stuff like this, cuz you can isolate a component and test it on its own. You also need to test the component integrated with other components, especially if focus is going between.

So we'll look at that and automated tests a little bit later. Any questions so far?
>> Just to make sure for the ARIA values, do they always have to be strings because I see that aria-expanded is string true or string false?
>> Great question. So, what gets rendered in the browser is usually string.

There's also Boolean attributes like inert where it's basically is the attribute present or not. The value doesn't really matter. In React and TypeScript, in some instances, it will complain at you if you have a string that should be, I don't know, a number or a Boolean. There's some aria-value things for sliders that kind of caught me by surprise.

When you use TypeScript, it will tell you what it expects. Otherwise, strings are usually fine. React lately has been complaining about tabindex. It really wants that to be a number. And if you use TypeScript, you'll know about that. If you use JSX in React, strings are fine. It's very weird.

So I always thought strings were fine. I have had to convert some of my attributes to be numbers and Booleans lately, because I`m using a lot more TypeScript lately. And it`s actually really awesome for accessibility, because a lot of the types are built in. So it can catch some accessibility issues, if you use an HTML element, use an attribute that's not allowed on that element, TypeScript can tell you.

It can also be a hindrance sometimes, like we saw on the web app workshop, inert is not really working well in React right now, so I had to do a workaround with refs. It's kind of a pain right now, but on the flip side of that TypeScript can be pretty helpful in a lot of cases.

I didn't love it for a long time, now I'm into it cuz it can be helpful. So this whole string number thing, you just satisfy the TypeScript compiler a lot of times and make sure that what's getting rendered is what you expect. Is it giving the right state in the browser and assistive technology which we can usually look at in the Chrome Dev Tools Accessibility inspector, it'll show you.

Cool, any other questions before we move on? We're gonna come back to this example and write some automated tests for it a little later.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now