Web components are a new set of APIs built on web standards that are widely adopted by browsers (see browser support at webcomponents.org). They allow developers to make flexible custom components–but with that flexibility comes responsibility. In this two-part blog, we will outline what web components are and the specific accessibility considerations they have, so you can integrate web components into your own products with all users in mind. You can read the second blog, all about accessibility for Web Components here.
- Custom elements
- HTML templates
- Shadow DOM
Salesforce’s Lightning Web Components (LWC) component framework is built on top of web components to make it easy to create fast, lightweight components. Let’s explore an example web component to see how we can best leverage them.
This is the custom tag itself, which extends either an existing tag (like HTMLButton) or the base HTMLElement.
For my example component, I am going to extend the base HTML element. I have to define the custom element for the browser and connect it to the CustomButton class I made (live finished CustomButton).
Right now, I have this awesome new tag
<custom-button></custom-button>, but it doesn’t have anything inside of it and it can’t do anything. There are a couple ways to build this component. I could add functionality directly to the custom tag, but in this example I will use HTML templates.
There are two ways to create reusable snippets of HTML:
Looking at the CustomButton, using a template makes sense for now. I don’t need a lot of flexibility since it is just a button that developers can pass a custom string to.
To begin building my component, I add a template tag in the DOM (Document Object Model) and add a button inside of it. Then, in the constructor I append the contents of the template to the custom element itself.
My button template has a span inside of it with default text that the user can then replace by passing a string to the custom element with the text attribute.
I also added a connectedCallback function, which is a web component lifecycle function that happens when the component is connected to the DOM. In that function I set the innerText of the button to the value passed from the custom component.
I can use the CustomButton in my HTML like this:
So now, if I use my CustomButton component, the browser DOM will look like this:
Slots allow flexibility, since they let you put anything within them. This is especially useful if you need to allow consumers of your component to add custom HTML. One thing to keep in mind is that slots require that shadow DOM is enabled to work correctly.
For my CustomButton component, people might want to add an icon – so I can use a slot! I update the contents of the template to be:
Someone using my button can add any icon in their HTML:
Which, if shadow DOM is enabled, the browser will render as:
For more on the differences between the two, check out Mozilla’s article on templates and slots.
Since I have to use shadow DOM for the icon slot, the next step is to look into what the shadow DOM is and how it works.
Up until this point, when I talk about the DOM, it is the main DOM that the browser generates – which is also called the light DOM. If you view the page source of a site, you can see the light DOM, every HTML element on the page.
The shadow DOM is a scoped document object model tree that is only within your custom element. If shadow DOM is enabled in your component, the component’s elements are in a separate tree from the rest of the page.
No Shadow vs Open vs Closed
Web components don’t need to have shadow DOM enabled, but if it is enabled, it can either be open or closed.
If the shadow DOM is open: the main DOM can’t access the sub tree in the traditional ways, but you can still access the sub tree with Element.shadowRoot. document.getElementById, other query selectors, and CSS from outside the component will not affect it.
There are very few instances where having a fully closed shadow is necessary and the current industry standard is to use open shadow.
To look at the source code for the CustomButton example, I enable the open shadow DOM like this:
The contents of the template are now added to the shadow root, not directly to the custom element.
Finishing the Custom Button
The HTML is the way I want it to be, so it is time to make the CustomButton interactive. When people click the button, I want to toggle the aria-pressed attribute so users know if it is pressed.
This is the final code for my CustomButton, I have added a couple functions:
- get ariaPressed: returns the value of the aria-pressed attribute on the button inside the custom-button element
- set ariaPressed: sets the value of the aria-pressed attribute on the button inside the custom-button element.
- connectedCallback: adds an onClick listener when the component connects to the DOM.
- handleClick: toggles the value of ariaPressed when the button is clicked
Now, I can add my custom button to my HTML like this:
And I can programatically set the ariaPressed property like this:
- Lightning Web Components
- Mozilla: Using Templates and Slots
- Mozilla: Web Components
- Google: Shadow DOM v1
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.