- WHATWG tracking issue
- OpenUI issue tracking initial discussions and WHATWG resolution to accept
elementInternals.type = 'button'
Web component authors often want to create custom elements that inherit the behaviors and properties of native HTML elements. These types of custom elements are referred to as "customized built-in elements" or just "customized built-ins". By customizing built-in elements, custom elements can leverage the built-in functionality of standard elements while extending their capabilities to meet specific needs. Some of the use cases enabled by customized built-ins are listed below.
- Custom buttons can provide unique styles and additional functionality, such as split or toggle button semantics, while still maintaining native button behavior such as being a popover invoker.
- Custom buttons can extend native submit button behavior so that the custom button can implicitly submit forms. Similarly, custom buttons that extend native reset button behavior can implicitly reset forms.
- Custom labels can provide additional functionality, such as tooltips and icons, while still supporting associations with labelable elements via the
forattribute or nesting a labelable element inside the custom label.
- A solution for customized built-in elements that provides an improvement over
extends/is, in terms of interoperability and functionality. - Supporting as many as customized built-in use cases as possible, though not necessarily all at once. Support for more
typevalues can be added over time.
- Deprecation of
extends/is. This is something that can be considered independently, onceelementInternals.typeaddresses developer needs sufficiently. - A declarative version of
elementInternals.type. This requires finding a general solution for declarative custom elements with declarativeelementInternals. This is a broader problem that should be explored separately.
The ElementInternals interface gives web developers a way to participate in HTML forms and integrate with the accessibility OM. This will be extended to support the creation of customized built-ins by adding a type property, which can be set to string values that represent native element types. The initial set of type values being proposed are listed below. Support for additional values may be added in the future.
'' (empty string)- this is the default value, indicating the custom element is not a customized built-inbutton- for button like behaviorsubmit- for submit button like behaviorreset- for reset button like behaviorlabel- for label like behavior
If elementInternals.type is assigned any other value, a "NotSupportedError" DOMException should be thrown.
elementInternals.type should only be set once. If elementInternals.type has a non-empty string value and is attempted to be set again, a "NotSupportedError" DOMException should be thrown. This works similar to how attachInternals throws an error if called on an element more than once.
Setting elementInternal.type allows the custom element to support additional attributes. The full list for each type is provided in the sub-sections below. If any of the properties have been set prior to setting elementInternals.type, the attribute will be "reset" to the default state for that type. Below is an example showcasing this with the disabled attribute.
class CustomButton extends HTMLElement {
static formAssociated = true;
constructor() {
super();
this.disabled = true;
this.internals_ = this.attachInternals();
this.internals_.type = 'button';
console.log(this.disabled); // logs `false`
}
}
customElements.define('custom-button', CustomButton);When elementInternals.type = 'button' is set in a custom element's constructor, the custom element will gain support for the attributes listed below.
disabledlabelsformpopovertargetpopovertargetactioncommandcommandforinteresttarget- currently experimental in Chromium
Below is an example showcasing a custom button being used as a popup invoker. When the custom button is activated, ex. via a click, div id="my-popover" will be shown as a popover.
class CustomButton extends HTMLElement {
static formAssociated = true;
constructor() {
super();
this.internals_ = this.attachInternals();
this.internals_.type = 'button';
}
}
customElements.define('custom-button', CustomButton); <custom-button popovertarget="my-popover">Open popover</custom-button>
<div id="my-popover" popover>This is popover content.</div>Like with native buttons, if the disabled attribute is set, a custom button cannot be activated and thus cannot invoke popovers.
Custom elements with elementInternals.type = 'submit' set will support the following attributes.
disabledlabelsformformActionformEnctypeformMethodformNoValidateformTargetnamevaluewillValidatevalidityvalidationMessagecheckValidityreportValiditysetCustomValiditynamevalue
Below is an example showcasing a custom submit button being used to submit a form. When the custom button is activated, ex. via a click, the form will be submitted and the page will navigate.
class CustomSubmitButton extends HTMLElement {
static formAssociated = true;
constructor() {
super();
this.internals_ = this.attachInternals();
this.internals_.type = 'submit';
}
}
customElements.define('custom-submit-button', CustomSubmitButton); <form action="http://www.bing.com">
<custom-submit-button>Submit</custom-submit-button>
</form>If the disabled attribute is set on a custom submit button, it cannot be activated and thus cannot submit forms.
Custom elements with elementInternals.type = 'reset' set will support the following attributes.
Custom elements with elementInternals.type = 'label' set will support the following attributes.
Below is an example showcasing a custom label being used to label a checkbox. When the custom label is activated, ex. via a click, the checkbox is also activated, resulting in its state changing to checked.
class CustomLabel extends HTMLElement {
static formAssociated = true;
constructor() {
super();
this.internals_ = this.attachInternals();
this.internals_.type = 'label';
}
}
customElements.define('custom-label', CustomLabel); <custom-label for='my-checkbox'>Toggle checkbox</custom-label>
<input type='checkbox' id='my-checkbox' />Order of precedence for used values: Element properties > ElementInternals properties > default properties via elementInternals.type
When elementInternals.type is set, the custom element will be assigned the same defaults as the corresponding native element. For example, if elementInternals.type = 'button' is set, the custom element's default ARIA role will become button and this will be the used role if no explicit role is specified by the author. If the author sets elementInternals.role, the value of elementInternals.role will be the used role, taking precedence over the default role. If the author sets the role attribute on the custom element, the value of the role attribute will be the used role, taking precedence over both elementInternals.role and the default role.
Per spec, attachInternals cannot be called on custom elements that are defined with extends. Therefore, it is not possible to create a custom element that is defined with extends and also sets elementInternals.type.
Setting elementInternals.type gives a custom element native element like behavior, but the custom element's appearance does not change. In other words, the custom element does not take on default, author-specified or user-specified styles from the native element.
Customized built-ins must be form-associated to participate in forms
Today, custom elements need to be defined as form-associated to participate in forms. This is done by including static formAssociated = true; in its definition. Customized built-ins created by setting elementInternals.type will also need to be defined with static formAssociated = true; to participate in forms.
A partial solution for this problem already exists today. Authors can specify the extends option when defining a custom element. Authors can then use the is attribute to give a built-in element a custom name, thereby turning it into a customized built-in element.
Both extends and is are supported in Firefox and Chromium-based browsers. However, this solution has limitations, such as not being able to attach shadow trees to (most) customized built-in elements. Citing these limitations, Safari doesn't plan to support customized built-ins in this way and have shared their objections here: WebKit/standards-positions#97 (comment). As such, extends and is are not on a path to full interoperability today.
The elementInternals.type proposal addresses many of the limitations with extends/is, including allowing customized built-ins to support shadow DOM. The proposal also has support from the WHATWG and multiple browser vendors (including Safari) as noted by a WG resolution here: openui/open-ui#1088 (comment).
If a declarative implementation is considered, how should it be done so it would align with the future creation of a declarative implementation for custom elements?
- Chromium : Positive
- WebKit : Positive based on openui/open-ui#1088 (comment)
- Gecko : No official signal, but no objections shared in the discussion here: openui/open-ui#1088 (comment)
WHATWG resolution to accept elementInternals.type = 'button'
Many thanks for valuable feedback and advice from:
Please leave feedback by following the instructions on the readme page to submit an issue, contribute to an existing issue, or submit a PR on the explainer document itself.