In part one, we covered what web components are and how to build one. Now, we can look at how this new technology currently affects accessibility. It is important to note that these considerations are for web components and extend to Lightning Web Components as well.
In HTML, you can define what an element is with a role (see: the ARIA Roles Model). This helps give users context on how to interact with the element.
There are explicit roles, which can be set with the role attribute. In this example, the
role=”alert” so assistive technologies (AT) know to read it immediately when it appears on the page.
There are also implicit roles (see: complete list of implicit roles). Some HTML elements don’t need the role attribute because it is already understood to have a role, like
<ul> is implied to have
role=”listitem”. Browsers and AT know how to announce these based on this common understanding of what these elements are.
The roles, however, can be brittle. If an element with an implicit role is not the direct child of the correct element, the browser may not understand the relationship between these elements anymore.
<li> elements in the
<ul> are inside of a custom list item component. Browsers will no longer recognize this as a list with X number of items. Instead, it sees a list whose children have
role=”presentation” (which means ‘no role’).
To fix this, you need to add the explicit roles instead. I have switched my list to use a
role=”list”. For the list items, I can either make the inner HTML of my component a
role=”listitem” or just add
role=”listitem” to the custom element. Now that the roles are explicit, the browser understands the list and AT will read it correctly.
Global HTML attributes and properties on custom elements
When you are making a custom element, what properties and attributes do is not implemented by default.
It is definitely not recommended to pass information to a child element with a global attribute or property name, but if for some reason it is required it needs the following to work:
- A setter function that takes the value from the custom element and puts it on the correct element.
- A getter function that returns the value from the correct element (not the one passed to the custom element)
- A function that checks if the value is on the custom element and removes it so that the browser isn’t confused.
This can be a lot to maintain because you will need to constantly be sure that the value doesn’t exist on both the custom element and the child element.
Using the customButton example, if I were to pass the
aria-pressed value like this:
It would not work because the attribute would stay on the custom element, which has
role=”presentation”, and not go to the actual button. The browser and assistive technology know what
aria-pressed does on an element with
So, when a developer puts (or updates)
aria-pressed on the customButton, the component needs to take the attribute off the custom-button tag and put it on the button inside instead.
This is not an ideal way to code because it seems redundant to continually check if the attribute is in both places and is prone to regressions. It is simpler to call the attribute on custom element something else that won’t be confused for a global attribute and pass that value to the child element.
ID referencing + Shadow DOM
ID referencing is critical for accessibility. Many HTML attributes (
aria-controls, etc.) take an ID to another element to establish a connection between the two. Developers also use DOM queries to set focus with
When you can’t
If there is a shadow boundary, even if it’s open, referencing IDs from outside the component is impossible because the web component’s DOM is separate.
Let’s say I have a custom-input component with a unique ID and I try to associate it with a label in that label’s HTML for attribute, like this:
It will not work because when it is rendered, the input is in a separate DOM tree from the label, which means the browser can no longer link them together.
If there are any attributes that require IDs, they need to be within the same shadow boundary. So, for this custom-input example, I would pass a label attribute to the component instead of having it outside:
When you can
If your component has a slot that is inside of the shadow boundary, it might seem like you cannot reference IDs within the slot, but you actually can! This is because the contents of slots are actually in the light DOM, not the component’s shadow DOM (even if the slot is within the shadow boundary!).
This is the form-element-container wrapper component that I can pass an input to, it standardizes the CSS for each of my form elements. In this component, the input element goes into a slot and I leave the label outside the component.
The browser will display the DOM like this:
tabindex=”-1” and Custom Elements
tabindex=”-1” is a great tool! It is used to make something focusable and clickable, but not in the tab order. An example use case for it could be a toast: when the toast appears, you can focus the whole thing and then a user can tab to the close button.
Currently, if you put tabindex=”-1” on a custom element with shadow DOM enabled, it makes all of the child elements non-tabbable. This is unexpected, since tabindex usually does not propagate, and makes it so only people using a mouse can click the elements. So, for the toast example: if the toast is a custom element, putting tabindex=”-1” will prevent users from ever being able to tab to the close button within the toast container.
The way around this is to be careful about what elements you put tabindex=”-1” on and to be sure to architect your component so that it will never need to be on the custom element itself.
Accessibility Object Model
The Accessibility Object Model (AOM) project will theoretically fix the current accessibility issues with web components. The project is currently being developed, but there is no timeline on when it will be officially released and working on all browsers.
So instead of doing:
Given that the Accessibility Object Model is not finished or fully adopted by browsers, it is important to still make web components accessible built today. When you are architecting your new web components, be sure to include these considerations so everyone can use your components.
- W3C: The Roles Model
- W3C: List of all Implicit Roles
- [GitHub Issue] ShadowDOM: tabindex=”-1” Makes Shadow Tree non-focusable
- AOM Github Repository
- AOM Browser Test (shows which AOM features your browser supports)
- Web Components and the AOM by Léonie Watson
About the Author
Lee White is an accessibility engineer at Salesforce, where she focuses on Lightning Platform accessibility. You can follow her on Twitter @shleewhite.