Web Components Community Group: 2022 Spec/API status

Draft Community Group Report

Latest published version:
none
Editors:
Westbrook Johnson
Alan Dávalos
Rob Eisenberg
Owen Buckley
Caleb Williams
Justin Fagnani
Feedback:
GitHub w3c/webcomponents-cg (pull requests, new issue, open issues)

Abstract

This is required.

Status of This Document

This specification was published by the Web Components Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

This is required.

GitHub Issues are preferred for discussion of this specification.

1. Introduction

This section is non-normative.

The initial slate of APIs that browsers shipped with web components "v1" left many important features and use-cases on a future to-do list, to be finalized out after the core custom element and shadow DOM features landed. While the web components GitHub issues and face-to-face meets have tracked and discussed many of these features individually, there hasn't been a comprehensive overview of what's left to "finish" web components.

This document tries to highlight the main features that are lacking from the web components spec that either block adoption for more developers and frameworks, or cause pain points for existing developers.

It's worth noting that many of these pain points are directly related to Shadow DOM's encapsulation. While there are many benefits to some types of widely shared components to strong encapsulation, the friction of strong encapsulation has prevented most developers from adopting Shadow DOM, to the point of there being alternate proposals for style scoping that don't use Shadow DOM. We urge browser vendors to recognize these barriers and work to make Shadow DOM more usable by more developers.

1.1 Browser Parity

We noticed the following specs already have a high degree of alignment from an implementation perspective, but support in browsers is still not equally distributed. Filling in these gaps would be a big win for users and developers alike for a more predictable web.

1.2 Spec Alignment

The following specs we see as highly valuable to the developer community for being able to deliver great web experiences to users when using Web Components. As it pertains to topics like Aria and SSR, these specs just need a little more alignment across browser implementors so that consensus can be achieved.

1.3 Table of Contents

Feature or Problem GitHub Issue(s) Status(?)
Children changed callback WICG/webcomponents#809 Uncertain
Composed Selection WICG/webcomponents#79 Partial consensus, divergent partial implementations
Constructable Stylesheets & adoptedStyleSheets WICG/webcomponents#468 Some implementation
Cross-root ARIA WICG/aom#169
WICG/aom#107
WICG/webcomponents#917
WICG/webcomponents#916
Uncertain
CSS Module Scripts WICG/webcomponents#759 Some implementation
CSS Properties and values inside shadow root
Custom Attributes Web Components CG Discussion Not addressed
Custom CSS State
Declarative CSS Modules WICG/webcomponents#939 Not Addressed
Declarative custom elements Strawperson Not addressed
Declarative Shadow DOM whatwg/dom#831 Partial consensus, some implementation
DOM Parts DOM Part API - First Step of Template Instantiation Uncertain
Form-Associated Custom Elements WICG/webcomponents#187 Some implementation
HTML modules WICG/webcomponents#645 Partial consensus, no implementations
Imperative Slot Assignment whatwg/html#3534 Partial Implementation
Lazy custom element definitions WICG/webcomponents#782 Uncertain
Open styling of shadow roots WICG/webcomponents#909 Not addressed
Scoped Element Registries WICG/webcomponents#716 Consensus
Styling children of slotted content
Theming WICG/webcomponents#864 Uncertain
--- --- ---

2. Children changed callback

2.2 Description

Provide a callback that triggers whenever the parser finishes parsing children or when a new child is inserted or an old child is removed.

2.3 Status

2.4 Initial API Summary/Quick API Proposal

No concrete API proposal exists so far but the discussions seem to refer to using the callback similar to the attributeChangedCallback.

A component using said callback would look like this:

class ChildrenChangedCallbackSample extends HTMLElement {
  childrenChangedCallback() {}
}

2.5 Key Scenarios

2.6 Concerns

2.7 Dissenting Opinion

2.9 Open Questions

3. Composed Selection

3.2 Description

Since existing Selection APIs can represent only a single selection in a document, bound to a single Range, the need for an API to represent ranges over a composed tree has been generally acknowledged since at least 2015.

This introduces a new API, getComposedRange(), that can return a StaticRange with endpoints in different shadow trees. It modifies several existing selection APIs, like setBaseAndExtent(), to support composed trees in a backwards-compatible way.

It also specifies a consistent behavior for getSelection(), which currently behaves differently across browsers when applied to a shadow root.

3.3 Status

3.4 Initial API Summary/Quick API Proposal

customElements.define('x-editor', class extends HTMLElement {
  connectedCallback() {
    super.connectedCallback();
    document.addEventListener('selectionchange', () => {
      const selection = window.getSelection();
      const range = selection.getComposedRange({ shadowRoots: [ this.shadowRoot ] });
      // use shadow-aware `range` to evaluate selection within this component
    })
  }
});

3.5 Key Scenarios

Selection does not work across or within shadow roots. This makes fully-featured rich-text editors impossible to implement with web components. Some of the web's most popular editors have issues that are blocked on this functionality:

3.6 Concerns

4. Constructable Stylesheets & adoptedStyleSheets

4.2 Description

Constructable Stylesheets and adoptedStyleSheets enable adding styles directly to DOM trees, e.g. document and shadow roots, without creating new DOM elements. Because a single stylesheet object can be adopted by multiple scopes, it also allows sharing of styles that can be centrally modified.

4.3 Status

4.4 Initial API Summary/Quick API Proposal

The following is an example of what this would look like in practice.

const sheet = new CSSStyleSheet();
sheet.replaceSync('a { color: red; }');

// Apply the stylesheet to a document:
document.adoptedStyleSheets = [sheet];

// Apply the stylesheet to a Shadow Root:
const node = document.createElement('div');
const shadow = node.attachShadow({ mode: 'open' });
shadow.adoptedStyleSheets = [sheet];

4.5 Key Scenarios

4.6 Concerns

From their standards position tracker, Safari has highlighted some of the following concerns:

4.7 Dissenting Opinion

4.9 Open Questions

5. Cross-root ARIA

5.2 Description

Shadow boundaries prevent content on either side of the boundary from referencing each other via ID references. ID references being the basis of the majority of the accessibility patters outlines by aria attributes, this causes a major issue in developing accessible content with shadow DOM. While there are ways to develop these UIs by orchestrating the relationships between elements of synthesizing the passing of content across a shadow boundary, these practices generally position accessible development out of reach for most developers, both at component creation and component consumption time.

Developers of a custom element should be able to outline to browsers how content from outside of their shadow root realtes to the content within it and visa versa. Cross-root ARIA Delegation would allow developers to define how content on the outside is mapped to the content within a shadow root, while Cross-root ARIA Reflection would define how content within a shadow root was made available to content outside of it.

5.3 Status

Implementors participating in bi-weekly Accessibility Object Model syncs with WICG have all been favorable to the delegation work and are interested in the reflection work as a tighly related API that maybe is a fast follower. Leo Balter is working on finalizing proposal text for the delegation API at which time Westbrook Johnson will apply similar teminology to the reflection API proposal.

5.4 Initial API Summary/Quick API Proposal

5.4.1 Delegation API

HTML


  <span id="foo">Description!</span>
  <template id="template1">
    <input id="input" autoarialabel autoariadescribedby />
    <span autoarialabel>Another target</span>
  </template>
  <x-foo aria-label="Hello!" aria-describedby="foo"></x-foo>

JS


  const template = document.getElementById('template1');

  class XFoo extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "open", delegatesAriaLabel: true, delegatesAriaDescribedBy: true });
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
  }

  customElements.define("x-foo", XFoo);

5.4.2 Reflection API

HTML


  <input aria-controlls="foo" aria-activedescendent="foo">Description!</span>
  <template id="template1">
    <ul reflectariacontrols>
      <li>Item 1</li>
      <li reflectariaactivedescendent>Item 2</li>
      <li>Item 3</li>
    </ul>
  </template>
  <x-foo id="foo"></x-foo>

JS


  const template = document.getElementById('template1');

  class XFoo extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "open", reflectsAriaControls: true, reflectsAriaActivedescendent: true });
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
  }
  customElements.define("x-foo", XFoo);

5.5 Key Scenarios

When developing many of the patterns outlines in the ARIA Authoring Practices Guide having this capability would allow for encapsulating responsibilities outlined by the role attribute in a shadow root.

5.7 Open Questions

6. CSS Module Scripts

6.2 Description

CSS Module Scripts allow JavaScript modules to import style sheets. They can then be applied to a document or shadow root using adoptedStyleSheets in the same way as constructable style sheets.

6.3 Status

6.4 Initial API Summary/Quick API Proposal

import styleSheet from "./styles.css" assert { type: "css" };

document.adoptedStyleSheets = [ styleSheet ];

6.5 Key Scenarios

6.5.1 Motivation

  • CSS modules will allow styles to be loaded in component definitions without adding <style> or <link> elements that need to be created in each element instance.
  • Authors today often use inlined CSS strings in JS modules, but many of them want to move the CSS into separate .css files.
  • Without CSS-in-JS it's difficult to ensure that CSS is loaded before the component is defined and rendered. CSS module scripts provide the same ordering guarantee.
  • Component authors may need to import a resource that is only available as an external .css file, and don't have control over the resource to wrap it in a JS module.

For further motivational details, see this explainer document: webcomponents/css-modules-v1-explainer.md at gh-pages · WICG/webcomponents.

6.6 Concerns

6.7 Dissenting Opinion

6.9 Open Questions

7. CSS Properties and values inside shadow root

7.2 Description

---

7.3 Status

7.4 Initial API Summary/Quick API Proposal

Summary or proposal based on current status; paragraph(s) and code.

7.5 Key Scenarios

---

7.6 Concerns

7.7 Dissenting Opinion

7.9 Open Questions

8. Custom Attributes

8.2 Description

Enable developers to create reusable custom behaviors that that can be declaratively applied to any element.

8.3 Status

8.4 Initial API Summary/Quick API Proposal

There is no issue or proposal yet. The following can serve as an initial idea, inspired by custom elements.

  class MaterialRipple extends Attr {
    // ownerElement inherited from Attr
    // name inherited from Attr
    // value inherited from Attr
    // ...

    connectedCallback () {
      // called when the ownerElement is connected to the DOM
    }

    disconnectedCallback () {
      // called when the ownerElement is disconnected from the DOM
    }

    attributeChangedCallback() {
      // called when the value property of this attribute changes
    }
}

customAttributes.define("material-ripple", MaterialRipple);

8.5 Key Scenarios

8.6 Concerns

No concerns yet.

8.7 Dissenting Opinion

No dissenting opinions yet.

8.9 Open Questions

9. Custom CSS State

9.2 Description

Build-in elements provided by user agents have certain “states” that can change over time depending on user interaction and other factors, and are exposed to web authors through pseudo classes. For example, some form controls have the “invalid” state, which is exposed through the :invalid pseudo-class.

Like built-in elements, custom elements can have various states to be in too, and custom element authors want to expose these states in a similar fashion as the built-in elements.

9.3 Status

9.4 Initial API Summary/Quick API Proposal

The CustomStateSet API allows component authors to expose internal component state for use in styling or other element-matching operations (such as querySelector

This is different from a custom element sprouting a class (via this.classList.add in any state added to the custom element can be seen as internal (similar to the :checked pseud-selector for input elements).

To allow for this operation, a set-like API is exposed at ElementInternals.prototype.states, meaning that only custom elements can apply custom states. An example might look like the following:

          
class FancyElement extends HTMLElement {
  #internals = this.attachInternals();

  constructor() {
    super();
    
    const root = this.attachShadow({ mode: 'open' });
    const button = document.createElement('button');
    button.innerText = 'Add clicked state';
    button.setAttribute('part', 'btn');
    root.append(button);

    this.addEventListener('click', function wasClicked() {
      this.#internals.states.add('--clicked');
      this.removeEventListener('click', wasClicked);
    });
  }
}
customElements.define('fancy-element', FancyElement);
          

Consumers of the fancy-element code can now take advantage of the :--clicked state in CSS or in any DOM querying API to modify or select the relevant element once clicked.

For example, to change the background of the element's btn part, a consuming developer could apply the following CSS:

          
:--clicked::part(btn) {
  background: rebeccapurple;
}
          

Alternatively, a consuming developer could call document.querySelectorAll(':--clicked') to target all elements with the custom state.

9.5 Key Scenarios

9.6 Concerns

10. Declarative CSS Modules

10.2 Description

---

10.3 Status

10.4 Initial API Summary/Quick API Proposal

Summary or proposal based on current status; paragraph(s) and code.

10.5 Key Scenarios

---

10.6 Concerns

10.7 Dissenting Opinion

10.9 Open Questions

11. Declarative custom elements

11.2 Description

A method of creating custom elements completely with declarative HTML, not requiring JavaScript.

11.3 Status

11.4 Initial API Summary/Quick API Proposal

The following, copied from the original strapwerson proposal, demonstrates how a declarative custom element could be defined with the need for JavaScript.

<definition name="percentage-bar">
    <template shadowmode="closed">
        <div id="progressbar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="{{\root.attributes.percentage.value}}">
            <div id="bar" style="width: {{\root.attributes.percentage.value}}%"></div>
            <div id="label"><slot></slot></div>
        </div>
        <style>
            :host { display: inline-block !important; }
            #progressbar { position: relative; display: block; width100%height100%; }
            #bar { background-color#36fheight100%; }
            #label { position: absolute; top0pxleft0pxwidth100%height100%text-align: center; }
        </style>
    </template>
</definition>

11.5 Key Scenarios

11.6 Concerns

11.7 Dissenting Opinion

No dissenting opinions yet.

11.9 Open Questions

12. Declarative Shadow DOM

12.2 Description

Declarative Shadow DOM is a mechanism to express Shadow DOM using only HTML, with no dependency on JavaScript, much like light DOM can be declaratively expressed today.

12.3 Status

12.4 Initial API Summary/Quick API Proposal

Below is an example taken from the web.dev blog on Declarative Shadow DOM.

<host-element>
  <template shadowroot="open">
    <slot></slot>
  </template>
  <h2>Light content</h2>>
</host-element>

12.5 Key Scenarios

Server-Side Rendering: Without Declarative Shadow DOM, servers cannot deliver complete websites that include web component content. Markup cannot be efficiently delivered and then hydrated with JavaScript client-side.

JavaScript-less environments: Many web components could be implemented without JavaScript, taking advantage of encapsulated DOM and styles. However, web components cannot currently be rendered by users who have JavaScript disabled. Developers who are more comfortable with markup than with scripting may avoid shadow DOM altogether due to its tight coupling with JavaScript..

12.6 Concerns

12.7 Dissenting Opinion

12.9 Open Questions

13. DOM Parts

13.2 Description

A mechanism to insert or replace content at specific locations within the DOM tree.

13.3 Status

13.4 Initial API Summary/Quick API Proposal

The following is a summary of the core types from the proposal.

interface Part {
    attribute any value;
    void commit();
};


interface NodePart : Part {
    readonly attribute Node node;
};


interface AttributePart : Part {
    constructor(Element element, DOMString qualifiedName, DOMString? namespace);
    readonly attribute DOMString prefix;
    readonly attribute DOMString localName;
    readonly attribute DOMString namespaceURI;
};


interface ChildNodePart : Part {
    constructor(Node node, Node? previousSibling, Node? nextSibling);
    readonly attribute Node parentNode;
    readonly attribute Node? previousSibling;
    readonly attribute Node? nextSibling;
}

13.5 Key Scenarios

13.6 Concerns

13.7 Dissenting Opinion

13.9 Open Questions

14. Form-Associated Custom Elements

14.2 Description

The form-associated custom elements APIs enable custom elements to participate in form submission and validation lifecycles.

14.3 Status

14.4 Initial API Summary/Quick API Proposal

The form-associated custom elements APIs are implemented within the attachInternals method on the HTMLElement prototype. Calling attachInternals returns an instance of an ElementInternals object which grants developers the ability to interact with form elements provided they designate their element as a form-associated element.

      
<form>
  <fancy-input name="fancy"></fancy-input>
</form>

<script>
  class FancyInput extends HTMLElement {
    static get formAssociated() {
      return true;
    }

    constructor() {
      super();
      this.#internals = this.attachInternals();
      this.#internals.setFormValue('I can participate in a form!');
    }
  }

  customElements.define('fancy-input', FancyInput);

  document.querySelector('form').addEventListener('submit', event => {
    event.preventDefault();
    const formData = new FormData(event.target);

    console.log(formData.get('fancy')); // logs 'I can participate in a form!'
  });
</script>
      

The setFormValue method can accept several types of data including strings, FileData and FormData objects, the latter of which can allow a nested form to participate with a parent in its entirety.

In addition to providing an method for adding a value to a form object, the form-associated APIs provide a surface to allow custom elements to participate in form validation.

          
class FancyInput extends HTMLElement {
  static get formAssociated() {
    return true;
  }

  constructor() {
    super();
    const root = this.attachShadow({ mode: 'open' });
    this.#internals = this.attachInternals();
    this.#internals.setFormValue('I can participate in a form!');
    const button = document.createElement('button');
    root.append(button);

    button.addEventListener('click', this.#onClick);
    this.button = button;
  }

  #onClick = () => {
    if (this.#internals.invalid) {
      this.#internals.setValidity(); // Marks the element as valid
    } else {
      this.#internals.setValidity({
        customError: true
        }, 'You must click the button', this.button); // Marks the element as invalid and will focus on the button when the form checks validity
  }
}
          

14.5 Key Scenarios

14.6 Concerns

14.8 Open Questions

15. HTML modules

15.2 Description

HTML Modules would provide a means of referencing HTML through the web's module system. For Web Components specifically, this would allow the usage of import to load and initialize the template of a custom element, much in the same way CSS Modules would work for an adoptedStyleSheet.

Additionally, this could open up the concept of Single File Components (SFC) to the web.

15.3 Status

15.4 Initial API Summary/Quick API Proposal

import template from 'my-template.html' as HTMLTemplateElement;

MyCustomElement extends HTMLElement {
  constructor() {
    const root = this.attachShadow({mode: 'closed'});
    root.appendChild(document.importNode(template));
  }
}

15.5 Key Scenarios

The primary motivation for HTML Modules is to round out ESM so that all of the three main web languages (HTML / CSS / JS) can be leveraged through a module system. For Web Components, this is especially helpful for scenarios related to Server Side Rendering or any form of templating, static or otherwise. Having a common mechanism in JavaScript to author and use Web Components makes for a great experience all around.

Additionally it will work great if you prefer to separate your technologies (e.g. my-component.(js|css|html) ), or your (CSS|HTML)-in-JS

in one file.

15.6 Concerns

15.7 Dissenting Opinion

15.9 Open Questions

16. Imperative Slot Assignment

16.2 Description

---

16.3 Status

16.4 Initial API Summary/Quick API Proposal

Currently the Slot API only supports a declarative API, meaning that slot usage can only be expressed through adding the name attribute on an element. But there are valid cases where from multiple sources of slotted content, the Web Components author may want to programmatically set the content of a slot instead. Take this example from the proposal.

<custom-tab show-panel="2">
  <tab-panel></tab-panel>
  <tab-panel></tab-panel>
  <tab-panel></tab-panel>
</custom-tab>

Without an imperative API, how would an author be able to set the contents of a single available slot to the selected panel index?

16.5 Key Scenarios

Some of the scenarios called out in the proposal include not having to pre-compute the slot names ahead of time, as well as being able to conditionally load content into a slot.

16.6 Concerns

16.7 Dissenting Opinion

16.9 Open Questions

17. Lazy custom element definitions

17.2 Description

Enable the browser to automatically load a custom element definition when it first sees the associated tag.

17.3 Status

17.4 Initial API Summary/Quick API Proposal

Below is a definition for the proposed new CustomElementRegistry API.

  CustomElementRegistry#defineLazy(tagName: string, loader: () => Promise);

17.5 Key Scenarios

17.6 Concerns

17.7 Dissenting Opinion

17.9 Open Questions

18. Open styling of shadow roots

18.2 Description

---

18.3 Status

18.4 Initial API Summary/Quick API Proposal

Summary or proposal based on current status; paragraph(s) and code.

18.5 Key Scenarios

---

18.6 Concerns

18.7 Dissenting Opinion

18.9 Open Questions

19. Scoped Element Registries

19.2 Description

Scoped element registries allow custom element definitions to be scoped to one or more shadow roots. This allows the same tag name to be used with different implementations in different parts of the page, greatly reducing tag name collisions.

19.3 Status

19.4 Initial API Summary/Quick API Proposal

Summary or proposal based on current status; paragraph(s) and code.

19.5 Key Scenarios

19.6 Concerns

19.7 Dissenting Opinion

19.9 Open Questions

20. Styling children of slotted content

20.2 Description

---

20.3 Status

20.4 Initial API Summary/Quick API Proposal

Summary or proposal based on current status; paragraph(s) and code.

20.5 Key Scenarios

---

20.6 Concerns

20.7 Dissenting Opinion

20.9 Open Questions

21. Theming

21.2 Description

Provide a solution for deep cross-shadow root styling of web components without the forwarding needed using ::part or the granularity needed using CSS variables.

21.3 Status

21.4 Initial API Summary/Quick API Proposal

The original proposal suggested using a similar syntax as with shadow parts.

<x-button>
  #shadow-root
  <button theme="button"></button>
</x-button>
:root::theme(button) { ... }

Further discussions after ::theme was initially dropped from the spec have also suggested creating new media query types for theming for better readability and allowing for versioning.

@media (theme-name: vaadin) and (theme-version: 2.0) {
  :is(vaadin-text-field, vaadin-select):dir(rtl)::part(input-field)::after {
    transform-origin: 0% 0;
  }
}
// registering the theme on the author side
this.attachShadow({ mode: 'open', theme: { name: 'vaadin', version: '2.0' }});

The media query solution wouldn't completely eliminate the need for the ::theme selector as it doesn't solve the issue with exporting shadow parts.

21.5 Key Scenarios

21.6 Concerns

21.7 Dissenting Opinion

21.9 Open Questions

22. Feature or Problem

22.2 Description

---

22.3 Status

22.4 Initial API Summary/Quick API Proposal

Summary or proposal based on current status; paragraph(s) and code.

22.5 Key Scenarios

---

22.6 Concerns

22.7 Dissenting Opinion

22.9 Open Questions

23. Conformance

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

This is required for specifications that contain normative material.

A. Index

A.1 Terms defined by this specification

A.2 Terms defined by reference