Copyright © 2024 the Contributors to the Web Components Community Group: 2022 Spec/API status Specification, published by the Web Components Community Group under the W3C Community Contributor License Agreement (CLA). A human-readable summary is available.
This is required.
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.
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.
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.
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.
Provide a callback that triggers whenever the parser finishes parsing children or when a new child is inserted or an old child is removed.
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() {}
}
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.
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
})
}
});
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:
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.
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];
<style>
elements for each style used in each shadow root has a measurable performance overhead.From their standards position tracker, Safari has highlighted some of the following concerns:
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.
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.
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);
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);
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.
delegation
and reflection
the right name for these APIs? Particularly, "reflection" is used in ARIA Reflection.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.
import styleSheet from "./styles.css" assert { type: "css" };
document.adoptedStyleSheets = [ styleSheet ];
<style>
or <link>
elements that need to be created in each element instance.For further motivational details, see this explainer document: webcomponents/css-modules-v1-explainer.md at gh-pages · WICG/webcomponents.
@import
statements in CSS Modules.---
Summary or proposal based on current status; paragraph(s) and code.
---
Enable developers to create reusable custom behaviors that that can be declaratively applied to any element.
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);
No concerns yet.
No dissenting opinions yet.
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.
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.
:state(--state)
; however, it appears as if the current status is fairly accepted right now.---
Summary or proposal based on current status; paragraph(s) and code.
---
A method of creating custom elements completely with declarative HTML, not requiring JavaScript.
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; width: 100%; height: 100%; }
#bar { background-color: #36f; height: 100%; }
#label { position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; text-align: center; }
</style>
</template>
</definition>
No dissenting opinions yet.
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.
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>
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..
A mechanism to insert or replace content at specific locations within the DOM tree.
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;
}
The form-associated custom elements APIs enable custom elements to participate in form submission and validation lifecycles.
ElementInternals
which includes the form-associated custom elements behavior; however, the initial PR doesn't include the actual form-association APIs.
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
}
}
FormData
object, adding to the HTMLFormElement.prototype.elements
object and in other submission formats.formStateRestoreCallback
callback only for restore events (such as back button or page refresh), but does not call the custom element reaction for autocomplete. Firefox currently does not ship any code referencing the formStateRestoreCallback
.
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.
import template from 'my-template.html' as HTMLTemplateElement;
MyCustomElement extends HTMLElement {
constructor() {
const root = this.attachShadow({mode: 'closed'});
root.appendChild(document.importNode(template));
}
}
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.Document
? As a string? (see the scope concern above)---
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?
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.
Enable the browser to automatically load a custom element definition when it first sees the associated tag.
Below is a definition for the proposed new CustomElementRegistry API.
CustomElementRegistry#defineLazy(tagName: string, loader: () => Promise);
---
Summary or proposal based on current status; paragraph(s) and code.
---
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.
Summary or proposal based on current status; paragraph(s) and code.
---
Summary or proposal based on current status; paragraph(s) and code.
---
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.
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.
---
Summary or proposal based on current status; paragraph(s) and code.
---
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.