- 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
for
attribute 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
type
values can be added over time.
- Deprecation of
extends
/is
. This is something that can be considered independently, onceelementInternals.type
addresses 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.
disabled
labels
form
popovertarget
popovertargetaction
command
commandfor
interesttarget
- 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.
disabled
labels
form
formAction
formEnctype
formMethod
formNoValidate
formTarget
name
value
willValidate
validity
validationMessage
checkValidity
reportValidity
setCustomValidity
name
value
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).
- 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: