Building an Accessible Disclosure Button – using Web Components

web components and accessibilityWeb components are the next step in building modern web applications and a great way to prototype accessible widgets.

Today we are going to build a disclosure widget, by extending the native HTML button element.

Web components are a collection of several standards (Templates, Shadow DOM, Custom elements, Imports) that allow us to create reusable elements and encapsulate the complexity of code and style into the component.


The support for Web components varies amongst browsers. So in order to use it now we are going to use the Polymer library.

Registering the component.

Before using our component, we need to register it. For usability and clean separation, we are going to declare our component in a separate file: w3c-disclosure.html

The first thing we need to add in our file, is to import the polymer library, in order to enable the shadow DOM, custom elements, …

<link rel="import" href="components/polymer/polymer.html">

Then we declare the component like this:

<polymer-element name="w3c-disclosure" extends="button">
  • The element name for the component, (‘w3c-disclosure’) must always contain a dash (-).
  • The extends directive indicates that the component is an extension of the existing HTML button element .

We also create a base template structure for the shadow DOM subtree, and a JavaScript constructor.

So now our component code looks like this:

<link rel="import" href="components/polymer/polymer.html">

<polymer-element name="w3c-disclosure" extends="button">
        Polymer('w3c-disclosure', {
            ready: function() {

 Using the component

On the page where the component will be used, you need to load the Polymer platform library before any other JavaScript library, in the <head> section.

<script src="components/platform/platform.js"></script>

Then we need to import the component description.

<link rel="import" href="w3c-disclosure.html">

Now we can use our component in the page like any HTML element:

<button is="w3c-disclosure">Expand section 1</button>

Custom elements that inherit from native elements are called type extension custom elements. They inherit from a specialized version of HTMLElement and that means it can’t be used directly as a tag (<w3c-disclosure></w3c-disclosure>), instead we need to use the ‘is’ syntax.

Adding accessibility logic and style.

So far our component does nothing more than a regular button. So we are now going to see how to extend the button behaviour to add our disclosure feature.

In the components declaration use the ‘attributes’ directive to declare added public attributes. For our button we are going to add two attributes: controlfor and expanded

<polymer-element name="w3c-disclosure" extends="button" 
attributes="controlfor expanded" on-click="{{buttonClick}}">
  • controlfor, references the disclosed section id
  • expanded, if present, the section will be expanded on page load
  • on-click attribute is bound to the ‘click’ event so the buttonClick function is called on each button click event

Now we update the component in the page, to use those attributes.

<button is="w3c-disclosure" controlfor="details1">Expand section 1</button>
<section id="details1">
    Lorem ipsum …

Next we need to add the button logic, into the Web Component JavaScript.

The ready function: is the component constructor. Called on the WebComponentsReady event.
With this function we initialize states and an aria attribute when the component is loaded.

ready: function() {
    // If the expanded attribute is present, then the initial state is expanded.
    if (this.expanded === null) this.expanded = false;
    else this.expanded = true;
    // We map the controlfor attribute to the aria-controls attribute.
    this.setAttribute('aria-controls', this.controlfor);
    // We set a pointer to the disclosed content = document.querySelector('#' + this.controlfor);

The buttonClick function: is called on every ‘click’ event.

When the button is clicked, we toggle the expanded state.

buttonClick: function(event, detail, sender) {
    this.expanded = !this.expanded;

The expandedChanged function: is called when the expanded variable has been updated.

The Polymer data-binding feature automatically links the ‘expanded’ attribute to expandedChanged function so the function is called when the value for the variable has changed.

In this function we set aria states, and toggle the disclosed content.

expandedChanged: function(oldValue, newValue) {
    // We map the expanded value to the aria-expanded state
    this.setAttribute('aria-expanded', newValue);
    // We hide/show the content using the hidden attribute = !newValue;
    //For backward compatibility, we also set the CSS display attribute
    if (newValue) { // expanded is true'block';
    } else {'none';


The widget has now all the logic implemented, and we are going to add a little icon to visually indicate the expanded state.

In the template area, we add the following CSS rules:

<style type="text/css">
    :host #icon:before {
        content:"?"; font-size: 1em;
    :host #icon.expanded:before {
        content:"?"; font-size: 1em;

Many other techniques can be used for displaying icons. We used this one, with the ‘content’ attribute, for simplicity, in our demo.

The :host element represents the custom element itself, and allows the component to be styled from outside.

One last little trick: when your page is hosted online, you may notice some delay, for the disclosure content, to be hidden, when the page loads. It is due to the time the browser needs to load Polymer and apply the component polyfill.

For this issue Polymer provides a solution that hides the page content, until all elements are loaded. Add the ‘unresolved’ attribute to body:

<body unresolved>

Example use of w3c-disclosure button

Multiple w3c-disclosure buttons could be used in a list to allow the user to collapse a set of fields down to a small set of headings, with the ability to open each one.

In conclusion.

If you already know HTML and JavaScript, Web Components are easy to create and give us a great way to extend and improve regular HTML elements. And thanks to the Polymer or X-tags polyfill libraries, you can use it right now to create out-of-the-box accessible widgets, or improve existing Web Components by adding ARIA roles and attributes.


Categories: Technical


Pete says:

Should this

Polymer(‘w3c-disclosure’, {
ready: function() {


Polymer(‘w3c-disclosure’, {
ready: function() {

} brace at end?

Cédric Trévisan says:

Yes, Thanks