Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 23 additions & 42 deletions PlatformProvidedBehaviors/explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,23 +232,15 @@ This ensures that element-specific properties like `behavior.form` and `behavior

### API design

The current API uses instantiated behaviors with a single `behaviors` property:
Behaviors are instantiated with `new` and passed to `attachInternals()`:

- `behaviors` option in `attachInternals({ behaviors: [...] })` accepts behavior instances.
- `behaviors` property on `ElementInternals` is a read-only `FrozenArray`.
- Developers hold direct references to their behavior instances.

*Note: An ordered array is preferred over a set because order may be significant for [conflict resolution](#behavior-composition-and-conflict-resolution). `behaviors` uses a `FrozenArray` because behaviors are immutable after attachment.*

**Pros:**
- Single property name.
- No array lookup or `instanceof` checks needed as developers hold direct references.
- *Future* developer-defined behaviors are simpler: just instantiate and attach.
- Consistent mental model: behaviors are objects you create and manage.

**Cons:**
- Requires developers to manage behavior instances themselves.
- More setup code compared to passing class references directly.
Developers hold direct references to their behavior instances. This aligns with the [W3C design principle that classes should have constructors](https://www.w3.org/TR/design-principles/#constructors) that allow authors to create and configure instances, and it extends naturally to future developer-defined behaviors that follow the same `new` + attach pattern.

*For future developer-defined behaviors:*

Expand All @@ -270,27 +262,6 @@ this._internals = this.attachInternals({ behaviors: [this._tooltipBehavior] });
this._tooltipBehavior.content = 'Helpful tooltip text';
```

#### Alternative 1: Class references

Pass behavior classes (not instances) to `attachInternals()`:

```javascript
// Attach a behavior during initialization (class reference).
this._internals = this.attachInternals({ behaviors: [HTMLSubmitButtonBehavior] });

// Access behavior state via named accessor.
this._internals.behaviors.htmlSubmitButton.formAction = '/custom';
```

**Pros:**
- Named access via `this._internals.behaviors.<behaviorName>` requires no iteration.
- Less setup code as developers don't manage behavior instances.

**Cons:**
- Platform instantiates the behavior, so constructor parameters aren't available. This conflicts with the [design principle that classes should have constructors](https://www.w3.org/TR/design-principles/#constructors) that allow authors to create and configure instances.
- Requires a `behaviors` interface for named access.
- *Future* developer-defined behaviors would need a way to name their behaviors.

### Behavior composition and conflict resolution

When multiple behaviors are attached to an element, they may provide overlapping capabilities. This section discusses strategies for resolving such conflicts.
Expand Down Expand Up @@ -839,17 +810,6 @@ Although this proposal currently focuses on custom elements, the behavior patter

## Open questions

### Should behavior properties be automatically exposed on the element?

The current proposal uses manual property delegation: developers create getters/setters that delegate to the stored behavior instance. This gives authors full control over their public API, avoids naming conflicts, and allows validation or side effects in setters. The tradeoff is boilerplate for each exposed property.

Two alternatives have been considered:

- **Automatic property exposure:** The platform adds behavior properties directly to the custom element (e.g., `btn.disabled = true` works without any getter/setter). This matches how native elements work but introduces naming conflicts, reduces API control, and feels "magical."
- **Opt-in automatic exposure:** Authors choose per-element whether properties are auto-exposed via an option like `exposeProperties: true`. This offers flexibility but adds API complexity.

Future behaviors like `HTMLCheckboxBehavior` or `HTMLInputBehavior` would require developers to write boilerplate for key properties (`checked`, `value`) that external code needs to access. A consistent approach across all behaviors should be decided before additional behaviors ship.

### Is there a better name than "behavior" for this concept?

The American English spelling of behavior throughout this proposal follows the [WHATWG spec style guidelines](https://wiki.whatwg.org/wiki/Specs/style#:~:text=Use%20standard%20American%20English%20spelling). However, the word "behavior" has some drawbacks:
Expand Down Expand Up @@ -1042,6 +1002,27 @@ customElements.define('custom-button', CustomButton);

`HTMLSubmitButtonBehavior` could itself be designed as a decorator, but decorators can't easily access `ElementInternals` or instance state during application. Decorators would need to coordinate with `attachInternals()` timing. Additionally, getting a reference to the behavior instance for property access (e.g., `behavior.formAction`) would require additional wiring.

### Alternative to API design: Class references

Pass behavior classes (not instances) to `attachInternals()`:

```javascript
// Attach a behavior during initialization (class reference).
this._internals = this.attachInternals({ behaviors: [HTMLSubmitButtonBehavior] });

// Access behavior state via named accessor.
this._internals.behaviors.htmlSubmitButton.formAction = '/custom';
```

**Pros:**
- Named access via `this._internals.behaviors.<behaviorName>` requires no iteration.
- Less setup code as developers don't manage behavior instances.

**Cons:**
- Platform instantiates the behavior, so constructor parameters aren't available. This conflicts with the [design principle that classes should have constructors](https://www.w3.org/TR/design-principles/#constructors) that allow authors to create and configure instances.
- Requires a `behaviors` interface for named access.
- *Future* developer-defined behaviors would need a way to name their behaviors.

## Accessibility, security, and privacy considerations

### Accessibility
Expand Down