Baking accessibility into components: how frameworks help

Complex components like date pickers, custom selects and modals can be tricky to get right. They can be tricky to make accessible. They need good internals, like sound semantics, keyboard usability and focus management. All well tested, preferably with a diverse set of real users. Once that is done, once a complex component is built to a high standard, frameworks or Web Components could help make that component very reusable.

Well, what’s new, you might think? Isn’t reusability of components the whole point of UI frameworks and Web Components? Yes, probably. But that is often pointed out as an advantage that increases developer convenience. I’ve heard that phrased as ‘if the developer has convenience, the user will, too’. Meanwhile, component framework-powered websites have a somewhat negative connotation among accessibility experts. This post tries to explore how component frameworks help. How they, too, can make the web a much better place for users, especially in terms of accessibility and usability. (Disclaimer: conclusions in this post may be trivial to declarative framework developers)

When I had to build a custom select

The longer a project goes on, the bigger the chance that someone will want a custom select to be built. I usually try and resist, because these are very hard to get right. Native selects have a lot of thought put into them. They work across platforms in a way that matches each platform (see native select behavior on Android, iOS). But sometimes there are good reasons. Really.

What I’m talking about, just to be clear, is not just a custom trigger (those are relatively easy), but also custom options within them. Everything custom!

The use case that initially triggered this piece of work, was a Mozilla project that had a component where someone could set field level privacy settings. There was a number of fields, like ‘First Name’ and ‘Last Name’, each with their own privacy setting. It was designed to have just the icon in collapsed state, and icons plus labels in the expanded state. An interface with all options spelled out for each field, would likely cause mental overhead. This is not a setting people set often, but we want it to be there, easy to find, because it is important to have control over your data privacy. With that in mind, it would make sense to show just an icon in the collapsed state.

No ARIA this time

ARIA is a fantastic initiative that essentially lets developers polyfill semantics. It involves adding attributes to an HTML element, in order to set accessibility-related meta information like the names, descriptions, roles and states of that element. It is specifically aimed at rich internet applications. Something to remember about it is that, while the ARIA suite adds new semantics to use in HTML, it is not a successor to the existing set of HTML elements. Those elements are still there and often very appropriate to use. The first rule of ARIA is not to use it.

Yep, it’s usually fine and appropriate to use existing semantic HTML elements.

The problem with the existing HTML element that is select, is this: browsers provide no way to customise what the options look like. But a select is not so different from a group of radiobuttons, right? Either let users pick one out of many. (I’m leaving the multiple attribute out of this, but let me just say they could map to checkboxes).

One big advantage of radiobuttons is that they are easy to customise, because you can visually hide them and then do whatever you like with their associated label element.

This is the basic version of a custom option:

<input type="radio" id="option-1" name="option-set" />
<label for="option-1">Rotterdam</label>

With this, you can apply a visually hidden technique to the input, use the for styling, and rely on the name to associate multiple options to be one thing. You could wrap the group of options in a fieldset and give that a maximum height. And probably a sensible (max?) width. Also, you’ll want to ensure it opens where there is space (and not outside the viewport).

Then, if your list of options is all done, there is one piece left: the thing that toggles the options. For this, I would suggest a button:

<button type="button">
  Choose city

If you want, you could use some ARIA to convey that this button controls your fieldset. This can be done with aria-controls, with an ID as its value (use the ID of the element you expand):

    Choose city

In Using the aria-controls attribute, Léonie Watson explains that although support is inconsistent, creating a “controls” relationship in the DOM can make a component more robust, while not getting in the way of users:

The presence of aria-controls won’t damage the User Experience (UX) for people using UA that don’t support it, but it will enormously improve the UX for those people whose browser/screen reader combinations do.

Another thing you can do to the button is set the expanded state, with aria-expanded. This takes a boolean:

   Choose city

The aria-expanded needs to reflect the actual expanded state, so when the custom select is open, set it to true, if not, set it to false.

So… say, you’ve done all this work, and you start using these custom selects across your website. One risk of reusing through copy/paste is that the code ends up in many places, and only in some of the places, all the markup is used as intended. This could be detrimental to the user experience. Maybe we could actually benefit from an abstraction here.

When component abstractions aid accessibility

If we do the above and make it available as a declarative component, perhaps React, Vue or an actual vanilla Web Component, one benefit is state. As we’ve seen in the above example, there are some things that rely on state, like the setting of aria-expanded. Declarative component frameworks make such settings trivial (once you’re up and speed with using the framework, that is, we’ll get into that later).

A second big advantage, is that it becomes one whole at the point of usage. Let’s say it is called custom-select.

Whenever we want to use custom-select, we do (pseudo code):

<custom-select options="options" />

In which options is some JavaScript object of options.

We declare it, we say what the options are, and in the DOM, it becomes a custom select with those options. This “becoming a custom select” might worry front-end folks, but it is not some sort of magic. We have described exactly what this means, how it should be done. With which semantics, keyboard behavior, focus styles and usability. These things are very important metrics for quality, they require careful consideration. The advantage of using some sort of component abstraction is that only one person or one team has to do this thinking. Others then ‘just’ use it, without worrying about the internals.

In my case, I’ve gone for a button that toggles a fieldset, but if there is a better way, custom-select can become whatever that better way is in the future. We need to come up with excellent internals, even be ready to improve them after a user test or accessibility. But all in all, I find this separation between internals and actual usage helpful.

A good example of a similar separation of concerns, from existing HTML, is the <video> element. It has a couple of internals that developers don’t need to worry about at the point of usage. We just need to tell it where our video and subtitles live. It then deals with internals for us: play buttons, closed captions, even multiple video formats… all the good stuff.

I’m more and more convinced that containment of ‘the good stuff’ can make the web better for users.

Increased complexity

There is a disadvantage to this nice containment of components using a framework: it’s complex. I mean, the tooling required to ship code is, because it requires advanced knowledge of JavaScript. That potentially scares people away. Complexity could reinforce privilege and moving all of a component into JavaScipt can turn full-stack developers into gatekeepers. Heydon Pickering describes there is a problem with full stack:

if you put someone in charge of all [the full stack], it’s highly likely they are going to be much weaker in some areas than others

In his post, Heydon identifies that what I call a component’s “internals” above are often built poorly. For two reasons: because improving internals now requires knowledge of complex frameworks and because the people specialising in them get undervalued. People who are good at writing front-end code that improves accessibility, but not at advanced JavaScript, might give up, at which point the web could lose out on accessible functionality. That’s sad.

I really like working on the detailed stuff that affects users: useful keyboard navigation, sensible focus management, good semantics. But I appreciate not every developer does. I have started to think this may be a helpful separation: some people work on good internals and user experience, others on code that just uses those components and deals with data and caching and solid architecture. Both are valid things, both need love. Maybe we can use the divide for good?


Modern websites commonly contain complex interaction patterns that aren’t default on the web. We may dislike that or prefer simplicity, but that’s a different (and also interesting) discussion. If we are going to code these complex interactions, it makes sense to contain the result in a component. Let’s assume we’ve worked hard on very accessible and usable ‘internals’, ran user tests and are confident that they are good. Then re-usability is a great thing, because the people who reuse, don’t need to worry about the internals. Like they wouldn’t worry about the internals of a video element when they use that. This is how I think frameworks can help make the web more accessible to users. And, ultimately, pave the way for more user convenience.

Update 31/1: In this post, I regard framework components and Web Components as one group for lack of a better word, but as Šime Vidas notes in the comments: if we make accessible components it makes more sense to do it in framework-agnostic Web Components than in a framework. To add to that, it might not only be tool-agnostic, but also more future proof.

Comments, likes & shares (1)