Design systems are a popular way for teams to flavor their design and development workflow. However, an often-missing ingredient in many design systems is a focus on accessibility best practices — especially when component libraries are involved.
In this talk, we’ll take a look at how you can mix some commonly-used components with the ingredients of accessibility. Pair this with best practices guidance in your documentation, and you’ll have the recipe for a delectably inclusive design system.
Kathleen McMahon: [0:07] Welcome, everyone. I'm Kathleen McMahon. I'm here today to talk to you about how accessibility-flavored React components can make your design system delicious. Before we begin, let's get some details out of the way.
[0:20] My slide deck will be posted after the presentation on Notist, including the links to resources that I briefly touch upon. The full URL will be available later today on Twitter. For now, you can take a look at https://noti.st/resource11. You can also follow me @resource11 on Twitter, Instagram, and GitHub.
[0:54] Now, here's an outline of what we're going to be covering today. Oh, yeah, I have an agenda. Why accessibility first? Design systems are a cookbook, design systems, and React. We'll talk about icons, buttons, inputs, and wrap up with some quick guidance on documentation.
[1:14] Let's back up, so I can introduce myself better. I'm a principal engineer at CarGurus and I race bikes very badly. Mostly, you'll see me in costume, bracing two laps to your six, at the back of the pack on a singlespeed bike, mostly, unless something happens like perhaps a pandemic kicking in our doors, then your racing season is postponed.
[2:20] Fast forward to today, and the industry is moving at a fast pace. [laughs] It can feel overwhelming to keep up, much less, find the place where you can thrive, especially if you have both design and engineering skills and love to use that fundamental stack.
[3:04] Before working at CarGurus, I was the tech lead for the O'Reilly Media Design System. I learned a lot about streamlining component libraries during my time there.
[3:16] If you've never worked on a design system, you'll learn that there are a lot of things to consider. Great design systems combine two key factors, user experience and the end user experience. That means the designers and the developers that are using your design system, and the customers that are consuming the apps that were built using that design system.
[3:47] However, creating a great design system can be tough with so many moving parts. If you are rebooting a design system, you will have to choose what to tackle first. If your design system team is small, you have to be very strategic.
[4:05] For example, if there is any business logic in your components, your first priority should be to extract that out and build your components with accessibility in mind. Fix your colors, your components, and then reboot your docks.
[4:23] You may ask why accessibility first? Well, our users have varied needs, and because so often accessibility is what is handled last, that sends a poor message to our users.
[4:38] If you've read the WebAIM Milligan report, the results are depressing. With the amount of errors found on pages, 97.8 percent, on labeled inputs, 59 percent, and 60.1 percent unnecessary earlier attributes added to the page, we are making the Web worse in the name of good intentions.
[5:05] While we have things like the Web Content Accessibility Guidelines to follow, which sets a low bar to surpass, we have been missing the mark in the industry when it comes to making sure all of our users can use our apps.
[5:20] Imagine, though, if you had accessibility baked into some commonly used components. A design system is the perfect place for this. I like to say your design system is a cookbook, and cookbooks have a personality.
[5:39] My mom is a serious fan of cooking, and lately, I have been enjoying digging through all the cookbooks that she has collected over the years to read how recipes have evolved over time.
[5:52] If you take a look at some of the cookbooks published in the 1940s through 1960s and look past the outdated views, you'll find interesting recipes, questionable food combinations that included jello with meat, my favorite.
[6:16] Also, there was an impressive level of detail paid to the structure of every single part of the cooking process. There's even a section on table settings and entertaining. This is very similar to how a design system works.
[7:25] That said, if your developers are unsure how to start building inclusive apps, empower them with your design system. Build in some features in your components to help them along. Then you can start celebrating when your co-workers make those apps accessible without you having to ask for it. Your components are your tried-and-true recipes.
[7:51] What kind is your reference material? Creating a component is like following a recipe. You start off with high-quality ingredients, that's your semantic HTML. You mix in your seasonings, just a touch of ARIA attributes, you follow the directions, that's your documentation, and read those helpful hints, those are the best practices provided to you.
[8:17] Let's take those principles and apply them to some components beginning with icons. Icons can be either informative or decorative. Informative icons need to be paired with a descriptive text so they can be perceivable by screen readers. Decorative icons need to be hidden from assistive technology, because they don't add enough significant value to be announced in your app.
[8:47] There is more than one way to create an accessible icon. Two of the most recent ways are SVGs and icon fonts. At O'Reilly, we initially used SVG icons in our design system combined into a spreadsheet. Yet, we ran into some problems when we start testing them.
[9:07] We discovered a bug when testing in Safari when using High Sierra as an operating system, where voice over would announce every single one of those 100 or so icons in the spreadsheet we provided. It made me sad. Small team, we had to think fast to find a different solution and keep moving.
[9:34] Icon fonts to the rescue. We converted all our icons to a font set for the time being and chose to revisit icons as SVGs using SVGs later, since that High Sierra bug has now been fixed in later versions of the macOS.
[9:52] Let's go over an example of how we make an accessible icon component using semantic HTML and the icon font technique. This is what you typically see as an icon font pattern in the wild, but it's not accessible.
[10:08] This is an accessible icon pattern, let's break it down. The span containing our icon font has been sprinkled with a pinch of aria-hidden="true" to hide the icon font from screen readers.
[10:23] The second span contains a descriptive name for our icon and that span has a visually hidden CSS class added to it. This will remove the visual presentation of that text, yet will keep that text available for screen readers.
[10:41] In the wrapping span, we use a CSS class to convert the spans native display property from inline to inline-block. This will allow us to support margin and padding customization on all four sides of the element, rather than just on the left and the right of the element.
[11:01] Notice, that the span are being used for all three elements here, this is on purpose for when we pair this component with a button. Now, before we refactor this pattern into React syntax, let's consider whether this icon is informative or decorative.
[11:22] If our icon is informative, we keep this markup as is, because informative icons should announce. However, if this icon is supposed to be decorative, we would need to add this aria-hidden attribute set it to true into the wrapping span to ensure the entire group is not announced to the screen readers.
[11:46] Now that we have an accessible icon pattern, let's pop this into a functional component and convert this syntax to JSX. We're going to change the class to class name, change that true string to a Boolean value, and itself, close that empty span element, and then voila.
[12:05] Now, this is a static icon component in JSX. Let's make this component more flexible and expand the syntax to support three incoming props -- icon hidden, icon name, icon title, and add some guardrails here.
[12:27] It's important to create guardrails for your components, so you can be sure that your developers are always using the accessibility features that you've mixed in for them.
[12:39] For our icon component, we have added three guardrails. The first guardrail we've set up is a check to see if the icon name that the developers passed into the component exists in our icon library. If that icon does not exist in the library, the component will not render in the app.
[12:58] The second guardrail we've added is to ensure that if the developer does not pass in a descriptive text property into icon title, that icon's default name will always be exposed as a fallback.
[13:14] This last guardrail we've added is the icon hidden prop. If the developer passes in icon hidden and sets it to true, the containing span will render in the DOM and attach aria-hidden="true" to that span element. If no icon hidden prop is passed in, the aria-hidden attribute is not attached to the wrapping span at all.
[13:41] This way, we are guaranteeing that the icon will read out to screen readers no matter what, unless the developer purposely specifies otherwise, by passing in the icon hidden value. This true or no pattern works very well with HTML attribute that only need to be added if the value exists. Aria-hidden is one of those.
[14:07] You may be saying, but what about SVGs? What if I haven't rolled my own icon set? There are great options out there to do this with SVGs as well. For example, Font Awesome is still a great option for this.
[14:25] In fact, Font Awesome now has an official React component that you can use. It has been built with accessibility in mind, which is fantastic. It's pretty straightforward to use this component, and it adds the right properties for you under the hood by default when it renders into the DOM.
[14:47] There is a slight bug, however, if you want to use this component as a standalone informative icon. Say you want to announce an icon as beverage in this case, and you pass in this aria-label prop and set it to beverage. Yes, that aria-label will render in the DOM, but because the SVG element already has an aria-hidden="true" attribute added by default, that aria-label will do nothing here.
[15:23] However, you can get around this by using the icon component we built and swapping out the span that uses the CSS class to attach a font and swap in the Font Awesome icon there. Then you can leverage the visually hidden span to make your icon informative.
[15:47] Let's talk buttons. Buttons perform an action on the page. Buttons should look and act like a button. Buttons get screen reader and keyboard functionality for free. You don't have to add anything extra to get the functionality for free. That's huge.
[16:08] Our high-quality ingredient here is the button element. We sprinkle in an aria-label prop to support at instances where we have multiple buttons on the page with the same name. This will give context to screen reader users.
[16:25] For example, say you have five buttons on the page that all have read more as their text, how will the screen reader user know what the purpose of that button? If you add an aria-label here to this button, you can make this button announce as reading about dinosaurs and that will give users more context.
[16:47] Now, if your app needs to support localization, Adrian Roselli recently wrote a great article with a different pattern that uses the aria described by attribute. That article will be shared in the resources.
[17:01] Now, this is an accessible button in JSX. If we want to have an icon button, we mix in our icon component here, and we will wrap the buttons contents in a span for positioning.
[17:20] Notice here, we are using inline-level elements inside this button. This is because you should not be nesting any interactive elements like other buttons, links, form elements, any other type of control inside a button's children. You should also not be passing in block-level elements like a p tag, or even a div.
[17:43] If you look up div, if you look at the computed property of a div, it renders as a block-level element. If you put those type of elements inside a button, that is not valid HTML. Only inline-level elements should be passed into as button children.
[18:04] This JSX is popped into a component's render method, and we're going to add some props like aria label, and add some onClick handler and disabled button support here along with the icon name, and we'll add in guardrail. The one guardrail we add here will ensure that if you don't pass in an icon name into your button's props, no icon will render in the button.
[18:29] Inputs. Inputs need labels and error messages. Labeled inputs give all users more context. No, placeholders are not labels. Avoid using placeholders, otherwise, your users will lose context. Placeholders are hard to style across browsers and they are not auto-translated.
[18:52] Build your input to minimize horizontal scrolling, you don't want to force the user to scroll horizontally, if they can help it. Make that max input width of 80 characters, and stack your labels above the input and your error messages below the input to support screen magnification users.
[19:11] Another pattern you could do is put your label above the input, then the error message right to the right of that label, that will also support screen magnification users.
[19:22] This is an accessible input pattern. It's a bit hard to read, so let's zoom in. We start with our high-quality ingredient, semantic HTML, and we pair our labels and error messages with the input.
[19:38] In JSX, we associate the label with the input by pairing the labels HTML for prop with the inputs ID value. This is how screen readers will know the purpose of this input.
[19:55] We'll also mix in key aria spices for validation. We're going to add some aria-invalid props, aria-required prop, and we'll also add in the aria-describedby prop, and pair the aria-describedby prop value with the same value as the error text ID value. We'll also add an aria-live prop into this component and set it to polite, so the errors will announce to screen readers at a convenient point in the sequence of announcing.
[20:37] To make this component more flexible, we're going to mix in disabled attribute support and some SyntheticEvent support, like onChange and onKeyPress event to capture the keyboard actions for this input. We're also going to use the true and null handling pattern for any of the props that use a Boolean value.
[21:00] Add your guardrails. If a developer is not passing a label into the component, the whole input will not render, neither will that icon. This will help guide the developer to follow best practices when implementing component, especially an input component, and soon it will become second nature to pair labels with input, and that will carry through no matter which library or framework you happen to use in the future.
[21:28] The same principle goes for error handling. If the developer does not pass in both the invalid prop and the error message into the component, this error message will not render.
[21:41] Documentation. Those massive design systems out there, definitely have some drool-worthy patterns for us to dream about. If your team is small, showing examples of the many ways a component can be used is a good first step.
[21:57] When we were rebooting the docs, we used Gatsby for our style guide to leverage the power of MDX, and used Storybook to document our core component playground. We deployed static instances of each site using XitesNow, which is now renamed for sale and has now derailed any other pun that I can make about the Now product. I'm bummed, but OK.
[22:32] I want to touch up on Storybook for a moment. Storybook is a great way to sandbox your components in isolation and play with the UI logic without the complexities of business logic. Now, Storybook supports MDX, which is glorious. You could make all your documentation in Storybook and have MDX pages as a companion to each of your components.
[22:57] I want to point out the accessibility add-on. The accessibility add-on is a must here. This add-on uses the axe-core engine under the hood and does a quick audit of your components. The add-on identifies errors, tells you how serious they are, and gives you steps to fix them before you start your manual accessibility testing.
[23:22] When documenting your components, you should add helpful hints to guide developers how to implement your components properly. For example, give guidance on how to make an informative icon or a decorative icon. Be sure to add props tables to your components so your developers know which prop does what and whether it's required.
[23:50] It's very important to add your component dos and don'ts to your documentation. You could make a whole suite of accessible components. However, it's very possible to take an accessible component and use it in excessibly, depending on the context in which you're using that component. Add those dos and don'ts and give that guidance for your developers.
[24:15] Dedicate a page to accessibility resources. The what kind success criteria can be intense to parse. If you've curated any articles that help explain how to implement common accessibility patterns, add those to your docs.
[24:31] To wrap up, our users are diverse. Your design system is a cookbook, and cookbooks have personality. React is a kitchen utensil and you are the cook. Components are your tried-and-true recipes. What kind is your reference material and document it all. Remember, dinosaurs are always the hotness. Thank you.