Skip to content

Commit 4adf38e

Browse files
authored
[Custom Elements with Native Element Behaviors] Add other considered alternative (#1153)
- Add `ElementInternals` feature decomposition approach, mostly based on comment whatwg/html#11061 (comment) - General description - Characteristics - Sample code - Advantages - Disadvantages
1 parent 182ab36 commit 4adf38e

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

ElementInternalsType/explainer.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,129 @@ A partial solution for this problem already exists today. Authors can specify th
255255
256256
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.
257257
258+
### `ElementInternals` feature decomposition approach
259+
260+
An alternative approach focuses on decomposing native element behaviors into granular, 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.
261+
262+
Key characteristics of this approach include:
263+
264+
- **Granular control**: Features like form submission, popover invocation, and labeling are exposed individually through `ElementInternals`.
265+
- **Explicit opt-in**: Each behavior is enabled via static properties and `ElementInternals` properties.
266+
- **Composable design**: Multiple behaviors can be combined on a single element.
267+
- **Clear semantics**: Each API explicitly defines the algorithms and behaviors it affects.
268+
- **Behavioral dependencies**: Some functionality may require additional behaviors to ensure full accessibility and usability. For example, enabling popover targeting alone requires button-like accessibility semantics, focusability, and activation behaviors to deliver a complete user experience. This differs from form association, which doesn't inherently include accessibility roles or interaction behaviors. For instance, a custom element with only `popoverTargetElement` functionality would need additional properties and behaviors to be fully usable:
269+
- **Accessibility**: Default ARIA role (`button`), accessible name computation, and focus management.
270+
- **Interaction**: Keyboard activation (Enter/Space), mouse click handling, and focus indicators.
271+
- **Visual feedback**: Focus rings.
272+
- **Integration**: Proper event dispatching
273+
274+
```js
275+
class CustomButton extends HTMLElement {
276+
static buttonActivationBehaviors = true;
277+
278+
constructor() {
279+
super();
280+
this.internals_ = this.attachInternals();
281+
}
282+
283+
get popoverTargetElement() {
284+
return this.internals_.popoverTargetElement ?? null;
285+
}
286+
287+
set popoverTargetElement(element) {
288+
this.internals_.popoverTargetElement = element;
289+
}
290+
291+
get commandForElement() {
292+
return this.internals_.commandForElement ?? null;
293+
}
294+
295+
set commandForElement(element) {
296+
this.internals_.commandForElement = element;
297+
}
298+
}
299+
300+
// Corresponding ElementInternals interface extensions
301+
partial interface ElementInternals {
302+
// Button activation behaviors
303+
attribute Element? popoverTargetElement;
304+
attribute DOMString popoverTargetAction;
305+
attribute Element? commandForElement;
306+
};
307+
```
308+
309+
**Supported attributes:**
310+
311+
When `static buttonActivationBehaviors = true` is set, the custom element would gain support for button activation-specific attributes:
312+
313+
- `popovertarget` - Targets a popover element to toggle, show, or hide
314+
- `popovertargetaction` - Indicates whether a targeted popover element is to be toggled, shown, or hidden
315+
- `command` - Indicates to the targeted element which action to take
316+
- `commandfor` - Targets another element to be invoked
317+
318+
**Supported properties:**
319+
320+
The `ElementInternals` interface would be extended with properties corresponding to the enabled behaviors:
321+
322+
For button activation behaviors:
323+
324+
- `popoverTargetElement` - Returns the Element referenced by the `popovertarget` attribute
325+
- `popoverTargetAction` - Returns the value of the `popovertargetaction` attribute
326+
- `commandForElement` - Returns the Element referenced by the `commandfor` attribute
327+
328+
**Supported Methods:**
329+
Unlike the main `behavesLike` proposal which provides access to methods like `checkValidity()`, `reportValidity()`, and `setCustomValidity()` through behavior-specific mixins, this feature decomposition approach would only expose properties and attributes.
330+
331+
This approach offers several benefits:
332+
333+
- **Clear semantics**: Each feature explicitly defines which specification algorithms it modifies.
334+
- **Flexible composition**: Web delopers can mix and match only the behaviors they need.
335+
- **Evolutionary path**: New behaviors can be added incrementally without breaking existing APIs
336+
- **Future-compatible**: Provides a foundation for reducing boilerplate, whether through userland solutions or platform support.
337+
338+
However, this approach also introduces the following trade-offs:
339+
340+
- **Developer burden**: Requires significant boilerplate to expose native element-like APIs.
341+
- **Discoverability**: It can be difficult to identify all the pieces needed to emulate a specific native element.
342+
- **Implementation complexity**: Involves maintaining a larger number of individual features.
343+
- **Reduced granularity benefits**: Since web authors may need to opt into multiple related behaviors to achieve complete functionality, this can diminish the benefits of the granular approach. While it might seem appealing to have highly granular options like `static canUsePopoverTarget = true`, in practice this cannot be implemented without also including accessibility semantics, focusability, click event handling, and other core native element behaviors.
344+
345+
The main difference between this approach and the `behavesLike` proposal is whether web developers should be able to compose different behavior bundles (e.g., combining button activation with label behaviors). However, such composition introduces significant complexity:
346+
347+
```js
348+
class CustomElement extends HTMLElement {
349+
static buttonActivationBehaviors = true;
350+
static labelBehaviors = true;
351+
352+
constructor() {
353+
super();
354+
this.internals_ = this.attachInternals();
355+
}
356+
357+
get popoverTargetElement() {
358+
return this.internals_.popoverTargetElement ?? null;
359+
}
360+
361+
set popoverTargetElement(element) {
362+
this.internals_.popoverTargetElement = element;
363+
}
364+
365+
get control() {
366+
return this.internals_.control ?? null;
367+
}
368+
369+
set htmlFor(value) {
370+
this.internals_.htmlFor = value;
371+
}
372+
}
373+
```
374+
375+
- **Conflicting semantics**: Combining button activation behavior with label behavior introduces ambiguity about the element's ARIA role (should it be `button` or have no corresponding role since labels don't have an implicit ARIA role?).
376+
- **Interaction conflicts**: When clicked, should the element trigger a `clickevent` (button behavior) and also transfer focus to a labeled control (label behavior)? This dual behavior would be confusing and potentially harmful to user experience.
377+
- **Specification complexity**: Each combination of behaviors would require careful specification of how conflicts are resolved, leading to an increase in edge cases.
378+
379+
Given these challenges, web authors would likely default to using single behavior bundles. This makes the composability of this approach more theoretical than practical, while adding unnecessary complexity to both implementation and specification.
380+
258381
## Stakeholder Feedback / Opposition
259382
260383
- Chromium : Positive

0 commit comments

Comments
 (0)