You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Address feedback on platform-provided mixins explainer (2) (#1243)
# Updates
- Added `mixinList` to main proposal.
- Dropped `Mixin` in `this.internals.mixins.htmlSubmitButton` calls to access state.
- Updated example of a design system button.
- Added two new subsections in "Future work" to mention user-defined mixins and mixins in native HTML elements.
- Minor nits.
-[whatwg/html#9110](https://github.com/whatwg/html/issues/9110) - Popover invocation from custom elements
@@ -73,14 +74,17 @@ This proposal is informed by:
73
74
74
75
## Proposed Approach
75
76
76
-
This proposal introduces a `mixins` option to `attachInternals()` and a read-only `mixins` property on `ElementInternals` which allows custom elements to attachand inspect specific native behaviors. This approach enables composition while keeping the API simple, allowing elements to adopt behaviors during initialization.
77
+
This proposal introduces a `mixins` option to `attachInternals()` and a `mixinList` property on `ElementInternals` which allows custom elements to attach, inspect, and dynamically update native behaviors. This approach enables composition while keeping the API simple, supporting both initialization-time configuration and runtime updates.
- ARIA defaults: Implicit roles and properties for accessibility.
106
110
107
-
### Accessing Mixin State
111
+
### Accessing mixin state
108
112
109
113
Platform-provided mixins expose useful public properties and methods of their corresponding native elements. Authors can expose these capabilities on their custom element's public API by defining accessors that delegate to the mixin state.
110
114
@@ -132,33 +136,76 @@ class CustomSubmitButton extends HTMLElement {
This ensures web authors don't have to reimplement the state logic that the mixin is supposed to provide.
153
157
154
-
### Composition via attachInternals
158
+
### Updating mixins dynamically
159
+
160
+
To support dynamic behavior changes (e.g., when the `type` attribute changes), `ElementInternals` exposes a settable `mixinList` property that allows developers to replace the entire mixin list at once:
When the `mixinList` is updated, the implementation compares the old and new lists:
173
+
174
+
| Scenario | Behavior |
175
+
|----------|----------|
176
+
| Mixin added | The mixin is attached. Its event handlers become active. Default ARIA role is applied unless overridden by `ElementInternals.role`. |
177
+
| Mixin removed | The mixin is detached. Its event handlers are deactivated. Mixin-specific state (e.g., `formAction`, `disabled`) is cleared to default values. |
178
+
| Mixin retained (in both lists) | The mixin's state is preserved. Its position in the list may change. |
179
+
180
+
*Note:* Mixin state is preserved when the custom element is disconnected and reconnected to the DOM (e.g., moved within the document). State is only cleared when a mixin is explicitly removed from `mixinList`.
181
+
182
+
#### Mixin state
183
+
184
+
When a mixin is removed from the list, its state is cleared. If the same mixin is added back later, it starts with default state:
- Behaviors are defined once during initialization, avoiding the complexity of managing behavior lifecycle (adding/removing) and state synchronization.
159
-
- Authors can define a single class that handles multiple modes (submit, reset, button) by checking attributes before attaching internals, without needing to define separate classes for each behavior.
160
-
- A child class extends the parent's functionality and retains access to the `ElementInternals` object and its active mixins, allowing for standard object-oriented extension patterns.
161
-
- While this proposal uses an imperative API, the design supports future declarative custom elements. Once a declarative syntax for `ElementInternals` is established, attaching mixins could be modeled as an attribute, decoupling behavior from the JavaScript class definition. Platform-provided mixins could be referenced by string identifiers in markup. The following snippet shows a hypothetical example of declarative usage and how platform-provided mixins could be attached via an attribute.
193
+
// Re-add the submit mixin — it starts with default state.
If web authors need to preserve state when swapping mixins, they should save and restore it explicitly.
201
+
202
+
### Other considerations
203
+
204
+
This proposal supports common web component patterns:
205
+
206
+
- Authors can define a single class that handles multiple modes (submit, reset, button) by updating `mixinList` at runtime in response to attribute changes, without needing to define separate classes for each behavior.
207
+
- A child class extends the parent's functionality and retains access to the `ElementInternals` object and its active mixins.
208
+
- While this proposal uses an imperative API, the design supports future declarative custom elements. Once a declarative syntax for `ElementInternals` is established, attaching mixins could be modeled as an attribute, decoupling behavior from the JavaScript class definition. The following snippet shows a hypothetical example:
162
209
163
210
```html
164
211
<custom-buttonname="custom-submit-button">
@@ -169,52 +216,83 @@ Passing behaviors to `attachInternals()` provides several advantages for web com
169
216
170
217
### Use case: Design system button
171
218
172
-
A design system button that uses the `type` attribute to determine platform behaviors, similar to native elements.
219
+
While this proposal only introduces `HTMLSubmitButtonMixin`, the example below references `HTMLResetButtonMixin` and `HTMLButtonMixin`to illustrate how switching would work once additional mixins become available in the future.
- Ability to inspect its own properties via `this._internals.mixins`.
243
-
244
-
*Note: In this proposal, the set of mixins is fixed when `attachInternals` is called. If the `type` attribute changes later, the element cannot dynamically swap mixins on the existing instance. To support dynamic type changes, web developers create a fresh instance of the element (which hasn't called `attachInternals` yet) with the new attribute and replace the old instance.*
245
-
246
-
```javascript
247
-
attributeChangedCallback(name, oldVal, newVal) {
248
-
// Only recreate if we've already initialized (mixins are locked).
249
-
if (name ==='type'&&this._internals) {
250
-
// Create a fresh instance. It hasn't run connectedCallback yet,
// The new instance will read this 'type' during its initialization.
256
-
replacement.setAttribute('type', newVal);
257
-
258
-
// Swap the old element with the new one.
259
-
// This triggers connectedCallback() on the new instance, allowing it
260
-
// to call attachInternals() with the new mixin set.
261
-
this.replaceWith(replacement);
262
-
}
263
-
}
264
-
```
327
+
- The `type` attribute can be changed at runtime to switch between behaviors.
328
+
- Mixin properties like `disabled` and `formAction` are accessible and can be exposed.
265
329
266
330
## Future Work
267
331
268
-
While this proposal focuses on form submission, the mixin pattern can be extended to other behaviors in the future:
332
+
The mixin pattern can be extended to other behaviors in the future:
269
333
270
334
- **Generic Buttons**: `HTMLButtonMixin` for non-submitting buttons (popover invocation, commands).
271
335
- **Reset Buttons**: `HTMLResetButtonMixin` for form resetting.
@@ -275,13 +339,25 @@ While this proposal focuses on form submission, the mixin pattern can be extende
275
339
- **Radio Groups**: `HTMLRadioGroupMixin` for `name`-based mutual exclusion.
276
340
- **Tables**: `HTMLTableMixin` for table layout semantics and accessibility.
277
341
342
+
### User-defined mixins
343
+
344
+
An extension of this proposal would be to allow web developers to define their own reusable mixins. Considerations for user-defined mixins:
345
+
346
+
- How would custom mixins be defined and registered? Extend `PlatformMixin` or a dedicated registry?
347
+
- Custom mixins would need access to lifecycle hooks (connected, disconnected, attribute changes) similar to custom elements.
348
+
- The same conflict resolution strategies that apply to platform mixins would need to work with user-defined mixins.
349
+
350
+
### Mixins in native HTML elements
351
+
352
+
This proposal currently focuses on custom elements, but the mixin pattern could potentially be generalized to all HTML elements (e.g., a `<div>` element gains button behavior via mixins). Extending mixins to native HTML elements would also raise questions about correctness and accessibility.
353
+
278
354
### Conflict Resolution
279
355
280
356
As the number of available mixins grows, we must address how to handle collisions when multiple mixins attempt to control the same attributes or properties. We should explore several strategies to make composition possible without getting unexpected or conflicting behaviors:
281
357
282
358
1. **Order of Precedence (Default)**: The order of mixins in the array passed to `attachInternals` determines precedence (e.g., "last one wins"). This is simple to implement but may hide subtle incompatibilities.
283
359
2. **Compatibility Allow-lists**: Each mixin could define a short list of "compatible" mixins that can be used in combination. Any combination not explicitly allowed would be rejected by `attachInternals`, preventing invalid states (like being both abutton and aform).
284
-
3.**Explicit Conflict Resolution**: If conflicts occur, the platform could require the author to explicitly alias or exclude specific properties.
360
+
3. **Explicit Conflict Resolution**: If conflicts occur, the platform could require the author to explicitly exclude specific properties.
285
361
286
362
### Future use case: Inheritance and composition
287
363
@@ -320,14 +396,7 @@ class CustomButton extends HTMLElement {
320
396
render() {
321
397
this.attachShadow({ mode: "open" });
322
398
this.shadowRoot.innerHTML = `
323
-
<style>
324
-
:host {
325
-
display: inline-block;
326
-
cursor: pointer;
327
-
border: 1pxsolid#767676;
328
-
padding: 2px6px;
329
-
}
330
-
</style>
399
+
<style>...</style>
331
400
<slot></slot>
332
401
`;
333
402
}
@@ -344,7 +413,7 @@ class ResetButton extends CustomButton {
0 commit comments