Skip to content

Commit d8c4331

Browse files
alexkengdandclark
andauthored
add comparison section (#1168)
* move buttonType to Future work * remove interestfor * add elementInternals.role * update stakeholder feedback * add comparison * fix typo Co-authored-by: Dan Clark <daniec@microsoft.com> * update comparision examples * update comparison example * remove redundant ``` * revise comparison example * update buttonType with "button" as default value * remove the element appearance section as it's more of a section for the behavesLike approach, not the main proposal. * update Comparison * fix typo --------- Co-authored-by: Dan Clark <daniec@microsoft.com>
1 parent 4dac485 commit d8c4331

1 file changed

Lines changed: 135 additions & 87 deletions

File tree

ElementInternalsType/explainer.md

Lines changed: 135 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Web component authors often want to create custom elements that have the activat
1515

1616
- Custom buttons can be [popover invokers](https://html.spec.whatwg.org/multipage/popover.html#popoverinvokerelement) while providing unique styles and additional functionality (as discussed [here](https://github.com/openui/open-ui/issues/1088)).
1717

18-
- Custom buttons can provide native [submit button](https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type-submit) behavior so that the custom button can implicitly [submit forms](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-submit). Similarly, custom buttons can also provide native [reset button](https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type-reset) behavior that can implicitly [reset forms](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-reset) (as discussed [here](https://github.com/WICG/webcomponents/issues/814)).
18+
- Custom buttons can provide native [submit button](https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type-submit) behavior so that the custom button can implicitly [submit forms](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-submit) (as discussed [here](https://github.com/WICG/webcomponents/issues/814)). Similarly, custom buttons can also provide native [reset button](https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type-reset) behavior that can [reset forms](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-reset) .
1919

2020
Currently, web developers face challenges when trying to implement these behaviors in custom elements. The existing customized built-in approach using `extends` and `is` provides native button functionality but lacks full cross-browser support. As a result, developers are forced to manually reimplement button behaviors from scratch, leading to inconsistent implementations, accessibility issues, and development overhead.
2121

@@ -25,12 +25,12 @@ This proposal addresses these challenges by introducing a standardized way for c
2525
- A solution to support key button activation use cases, particularly command invocation and form submission
2626

2727
### Non-goals
28-
- Providing a comprehensive alternative to the customized built-in solution (`extends` and `is`), i.e., enabling a custom element to do everything a native button does.
28+
- Providing an alternative to the customized built-in solution (`extends` and `is`), i.e., enabling a custom element to do everything a native button does.
2929
- A declarative version of this proposal. This requires finding a general solution for declarative custom elements, which should be explored separately.
3030

3131
## Proposal: add static `buttonActivationBehaviors` property
3232
We propose enabling web component authors to create custom elements with button activation behaviors by adding a static `buttonActivationBehaviors` property to their custom element class definition.
33-
This proposal focuses on decomposing native element behaviors into specific functionalities that can be individually exposed through `ElementInternals`. This approach builds on the existing pattern established by form-associated custom elements ([FACEs](https://html.spec.whatwg.org/dev/custom-elements.html#form-associated-custom-elements)) and accessibility semantics ([ARIAMixin](https://www.w3.org/TR/wai-aria-1.2/#ARIAMixin)), where specific capabilities are exposed as discrete APIs that web developers can combine as needed.
33+
This approach builds on the existing pattern established by form-associated custom elements ([FACEs](https://html.spec.whatwg.org/dev/custom-elements.html#form-associated-custom-elements)) and accessibility semantics ([ARIAMixin](https://www.w3.org/TR/wai-aria-1.2/#ARIAMixin)), where specific capabilities are exposed as discrete APIs that web developers can combine as needed.
3434

3535
```js
3636
class CustomButton extends HTMLElement {
@@ -63,8 +63,6 @@ partial interface ElementInternals {
6363
};
6464
```
6565

66-
**Note**: Future work may include adding support for the `interestfor` attribute, which is currently [shipping in Chromium](https://chromestatus.com/feature/4530756656562176?gate=4768466822496256) as an experimental feature for interest-based element targeting.
67-
6866
**Implicit behaviors:**
6967
Beyond attributes, properties, and events, custom elements with `buttonActivationBehaviors = true` also gain native behaviors related to button activation:
7068
- **Click event activation**: The element fires click events when activated via mouse click, Enter key, Space key, or other activation methods
@@ -74,9 +72,6 @@ Beyond attributes, properties, and events, custom elements with `buttonActivatio
7472
### Order of precedence regarding ARIA role
7573
The order is `<custom-button role=foo>` > `ElementInternals.role` > default `button` role via `buttonActivationBehaviors`
7674

77-
### `buttonActivationBehaviors` does not change element appearance
78-
Setting `buttonActivationBehaviors` gives a custom element button activation behaviors, 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 that target the native button element, since the custom element has a different tag name (e.g., `<fancy-button>` instead of `<button>`).
79-
8075
## Examples
8176
### Custom button with popover invocation
8277
This example shows how to create a custom button that can invoke a popover element using the `commandfor` and `command` attributes:
@@ -141,30 +136,86 @@ customElements.define('custom-button', CustomButton);
141136
button.command = 'show-modal';
142137
</script>
143138
```
139+
## Comparison
140+
The following examples demonstrate how much JS code can be saved with this proposal when a custom element author wants to support `command`/`commandfor` attributes:
141+
### Without `buttonActivationBehaviors`
142+
```js
143+
class CustomButton extends HTMLElement {
144+
constructor() {
145+
super();
146+
this.internals_ = this.attachInternals();
147+
}
148+
149+
connectedCallback() {
150+
// ARIA role assignment
151+
this.internals_.role = 'button';
144152

145-
## Add `buttonType` property in `ElementInternals`
146-
To provide submit and reset functionality, this proposal also introduces a `buttonType` property to `ElementInternals` that controls the behavior when the custom element is activated.
153+
// Make element focusable
154+
if (!this.hasAttribute('tabindex')) {
155+
this.tabIndex = 0;
156+
}
147157

148-
The `ElementInternals` interface would be extended with:
149-
- `buttonType` - controls the activation behavior of the button (values: "button", "submit", "reset")
158+
this.addEventListener('click', this.handleClick);
159+
}
160+
161+
handleClick() {
162+
const targetId = this.getAttribute('commandfor');
163+
const command = this.getAttribute('command');
164+
const target = document.getElementById(targetId);
165+
166+
if (target) {
167+
if (command === "toggle-popover") {
168+
target.togglePopover();
169+
} else if (command === "show-popover") {
170+
target.showPopover();
171+
} else if (command === "hide-popover") {
172+
target.hidePopover();
173+
} else if (command === "show-modal") {
174+
target.showModal()
175+
} else if (command === "close") {
176+
target.close()
177+
} else if (command === "request-close") {
178+
target.requestClose()
179+
}
180+
181+
// Create and fire command event at the target element
182+
const commandEvent = new CustomEvent('command', {
183+
bubbles: true,
184+
cancelable: true
185+
});
186+
commandEvent.command = command;
187+
target.dispatchEvent(commandEvent);
188+
}
189+
}
190+
}
191+
customElements.define('custom-button', CustomButton);
192+
```
193+
### With `buttonActivationBehaviors`
194+
```js
195+
class CustomButton extends HTMLElement {
196+
static buttonActivationBehaviors = true;
197+
}
198+
customElements.define('custom-button', CustomButton);
199+
```
200+
201+
## Future Work
202+
### Add `buttonType` property on `ElementInternals`
203+
To support _submit_ and _reset_ functionality, this proposal can be extended with a new `buttonType` property on `ElementInternals`, which defines how the custom element behaves when activated:
204+
205+
- `"button"` - (Default) No special form behavior, only fires click events and command invocation
206+
- `"submit"` - Submits the associated form when activated
207+
- `"reset"` - Resets the associated form when activated
150208
151209
If `buttonType` is set to any other value, it will fall back to the default value.
152210
153211
**IDL definitions:**
154212
```webidl
155213
partial interface ElementInternals {
156-
[CEReactions] attribute DOMString buttonType;
214+
attribute DOMString buttonType;
157215
};
158216
```
159217
160-
**Activation behaviors:**
161-
- `"button"` - No special form behavior, only fires click events and command invocation
162-
- `"submit"` - Submits the associated form when activated
163-
- `"reset"` - Resets the associated form when activated
164-
165-
Following the [HTML specification for button's type attribute](https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type), the default `buttonType` is `"submit"` unless either the `command` or `commandfor` content attributes are present, in which case the default is `"button"`.
166-
167-
### `buttonType` and `formAssociated`
218+
#### `buttonType` and `formAssociated`
168219
When `buttonActivationBehaviors` is set to true, the custom element's form association behavior depends on the `buttonType` value:
169220
170221
- **`buttonType = "submit"` or `"reset"`**: The element automatically becomes form-associated and `static formAssociated = true/false;` becomes a no-op.
@@ -195,7 +246,7 @@ If any of these properties are accessed on a custom element that does not have b
195246
**Implicit behaviors:**
196247
- **Form submission**: When associated with a `<form>`, pressing Enter on an associated form control (e.g., a text input) will trigger submit behavior if the custom element `buttonType` is `"submit"`.
197248
198-
### Example
249+
#### Example
199250
200251
```js
201252
class CustomButton extends HTMLElement {
@@ -230,7 +281,66 @@ customElements.define('custom-button', CustomButton);
230281
</custom-button>
231282
</form>
232283
```
284+
### Open questions
285+
If additional behaviors are introduced in the future, how should potential conflicts be addressed? For example:
286+
287+
- **Conflicting semantics**: How should we handle ambiguity when combining command invocation behavior with label behavior? Should the element have an ARIA role of `button`, or none at all since labels lack an implicit ARIA role?
288+
- **Interaction conflicts**: When clicked, the [Invoker Commands API](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API) will automatically trigger command invocation, but should the element also transfer focus to a labeled control (label behavior)? Would this dual behavior create confusion or negatively impact the user experience?
289+
- **Specification complexity**: How can we define conflict resolution for these combinations without introducing excessive edge cases and complexity?
290+
291+
```js
292+
class CustomElement extends HTMLElement {
293+
static buttonActivationBehaviors = true;
294+
static labelBehaviors = true;
295+
296+
constructor() {
297+
super();
298+
this.internals_ = this.attachInternals();
299+
}
300+
301+
get commandForElement() {
302+
return this.internals_.commandForElement ?? null;
303+
}
304+
305+
set commandForElement(element) {
306+
this.internals_.commandForElement = element;
307+
}
308+
309+
get control() {
310+
return this.internals_.control ?? null;
311+
}
312+
313+
set htmlFor(value) {
314+
this.internals_.htmlFor = value;
315+
}
316+
317+
// Manual handling of label behavior (command invocation is handled automatically)
318+
handleLabelClick(event) {
319+
// Should this also transfer focus to labeled control (label behavior)?
320+
// Developers must resolve this conflict manually since command invocation
321+
// is automatically handled by the Invoker Commands API
322+
if (this.control) {
323+
// Label behavior: focus the labeled control
324+
this.control.focus();
325+
}
326+
}
327+
328+
connectedCallback() {
329+
// Manual role conflict resolution - developers must decide
330+
// whether this should be a button or label
331+
this.internals_.role = 'button'; // or no role for label behavior?
332+
333+
// Manual focus management for button behavior
334+
if (!this.hasAttribute('tabindex')) {
335+
this.tabIndex = 0;
336+
}
233337

338+
// Manual event handling for label behavior (command invocation is automatic)
339+
this.addEventListener('click', this.handleLabelClick.bind(this));
340+
}
341+
}
342+
customElements.define('custom-element', CustomButton);
343+
```
234344
## Alternatives considered
235345
236346
### 1. ElementInternals feature decomposition approach
@@ -317,7 +427,7 @@ The `ElementInternals` Interface would be extended with minimal command invocati
317427
**Trade-offs of this approach**
318428
- **Accessibility risks**: Without automatic defaults, developers may forget to implement critical accessibility features, leading to inaccessible custom elements.
319429
320-
We consulted the [ARIA Working Group](https://github.com/w3c/aria/issues/2637) on this approach versus the main proposal with built-in defaults (implicit behaviors), and the [overwhelming consensus](https://www.w3.org/2025/09/25-aria-minutes.html#d0af) was to provide accessibility defaults that can be potentially overwritten by "power users" (main proposal).
430+
We consulted the [ARIA Working Group](https://github.com/w3c/aria/issues/2637) on this approach versus the main proposal with built-in defaults (implicit behaviors), and the [overwhelming consensus](https://www.w3.org/2025/09/25-aria-minutes.html#d0af) was to provide accessibility defaults that can be potentially overwritten by "power users" through `elementInternals.role`.
321431
322432
### 2. Static `behavesLike` property with behavior-specific interface mixins
323433
This alternative approach enables web component authors to create custom elements with native behaviors by adding a static `behavesLike` property to their custom element class definition. This property can be set to string values that represent native element types:
@@ -538,73 +648,11 @@ A partial solution for the key use cases described above already exists today. A
538648
539649
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: https://github.com/WebKit/standards-positions/issues/97#issuecomment-1328880274. As such, `extends` and `is` are not on a path to full interoperability today.
540650
541-
### Future Considerations
542-
If additional behaviors are introduced in the future, how should potential conflicts be addressed? For example:
543-
544-
- **Conflicting semantics**: How should we handle ambiguity when combining command invocation behavior with label behavior? Should the element have an ARIA role of `button`, or none at all since labels lack an implicit ARIA role?
545-
- **Interaction conflicts**: When clicked, the [Invoker Commands API](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API) will automatically trigger command invocation, but should the element also transfer focus to a labeled control (label behavior)? Would this dual behavior create confusion or negatively impact the user experience?
546-
- **Specification complexity**: How can we define conflict resolution for these combinations without introducing excessive edge cases and complexity?
547-
548-
```js
549-
class CustomElement extends HTMLElement {
550-
static buttonActivationBehaviors = true;
551-
static labelBehaviors = true;
552-
553-
constructor() {
554-
super();
555-
this.internals_ = this.attachInternals();
556-
}
557-
558-
get commandForElement() {
559-
return this.internals_.commandForElement ?? null;
560-
}
561-
562-
set commandForElement(element) {
563-
this.internals_.commandForElement = element;
564-
}
565-
566-
get control() {
567-
return this.internals_.control ?? null;
568-
}
569-
570-
set htmlFor(value) {
571-
this.internals_.htmlFor = value;
572-
}
573-
574-
// Manual handling of label behavior (command invocation is handled automatically)
575-
handleLabelClick(event) {
576-
// Should this also transfer focus to labeled control (label behavior)?
577-
// Developers must resolve this conflict manually since command invocation
578-
// is automatically handled by the Invoker Commands API
579-
if (this.control) {
580-
// Label behavior: focus the labeled control
581-
this.control.focus();
582-
}
583-
}
584-
585-
connectedCallback() {
586-
// Manual role conflict resolution - developers must decide
587-
// whether this should be a button or label
588-
this.internals_.role = 'button'; // or no role for label behavior?
589-
590-
// Manual focus management for button behavior
591-
if (!this.hasAttribute('tabindex')) {
592-
this.tabIndex = 0;
593-
}
594-
595-
// Manual event handling for label behavior (command invocation is automatic)
596-
this.addEventListener('click', this.handleLabelClick.bind(this));
597-
}
598-
}
599-
customElements.define('custom-element', CustomButton);
600-
```
601-
602-
These questions are important for long-term design, but they are outside the scope of this proposal. Our goal here is to acknowledge them without attempting to resolve them.
603651
604652
## Stakeholder Feedback / Opposition
605653
- Chromium: Positive
606-
- WebKit: No official signal
607-
- Gecko: No official signal, but no objections shared in the discussion here: https://github.com/openui/open-ui/issues/1088#issuecomment-2372520455
654+
- WebKit: No [official signal](https://github.com/WebKit/standards-positions/issues/513)
655+
- Gecko: No [official signal](https://github.com/mozilla/standards-positions/issues/1250)
608656
609657
[WHATWG resolution to accept `elementInternals.type = 'button'`](https://github.com/openui/open-ui/issues/1088#issuecomment-2372520455)
610658

0 commit comments

Comments
 (0)