Skip to content

Web Component Limitations

Jan Miksovsky edited this page Oct 20, 2016 · 3 revisions

Checklist » Web Component Limitations

This page attempts to capture limitations of the current web component APIs that make it difficult or impossible to create a web component that's as good as a standard HTML element.

Some element aspects must be set through attributes during initialization

The Requirements for custom element constructors dictate that a constructor may not add attributes to a new element. Since standard HTML elements created with document.createElement() never have attributes, the expectation is that custom elements shouldn't either. However, there are situations in which a custom element can accomplish a desired result only through adding attributes to itself.

The canonical example here might be ARIA support, which is strictly managed through attributes. A list-like custom element might want to provide a default ARIA role by setting role="listbox" on itself during initialization. However, per the above rules, this cannot be done in the constructor:

class ListBox extends HTMLElement {
  constructor() {
    super();
    this.setAttribute('role', 'listbox'); // This throws an exception.
  }
}

The next-best way to handle this situation might be a connectedCallback.

class ListBox extends HTMLElement {
  connectedCallback() {
    this.setAttribute('role', 'listbox');
  }
}

However, this is suboptimal, for two reasons. First, it's surprising that the element will magically gain an attribute when it's added to the document:

let listBox = document.createElement('basic-list-box');
let roleBeforeAdding = listBox.getAttribute('role'); // null
document.appendChild(listBox);
let roleAfterAdding = listBox.getAttribute('role'); // Now equals 'listbox'. Surprise!

Second, the above component definition could overwrite attributes that were added to the element after it was constructed and before it was added to the document:

let listBox = document.createElement('basic-list-box');
listBox.setAttribute('role', 'tabs'); // Set custom role
document.body.appendChild(listBox); // connectedCallback will overwrite role!

To avoid this problem, a custom element should check if it an attribute has already been set before applying a default value:

class ListBox extends HTMLElement {
  connectedCallback() {
    if (!this.getAttribute('role')) {
      this.setAttribute('role', 'listbox');
    }
  }
}

let listBox = document.createElement('basic-list-box');
listBox.setAttribute('role', 'tabs'); // Set custom role
document.body.appendChild(listBox); // connectedCallback leaves role alone

This avoids the second problem above, but is still subject to the first problem of potentially surprising behavior.

For a deeper analysis of this problem, including proposals for possible long-term fixes in the platform, see Gap Analysis: Accessibility.

You can’t perform that action at this time.