Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ sync'd July 30, 2025
| JSON dummy data | Simple JSON files. Used for [View a JSON file or server response with formatting](https://learn.microsoft.com/microsoft-edge/web-platform/json-viewer). | [/json-dummy-data/](https://github.com/MicrosoftEdge/Demos/tree/main/json-dummy-data) | [JSON dummy data](https://microsoftedge.github.io/Demos/json-dummy-data/) (Readme) |
| OpaqueRange | Demonstrates the `OpaqueRange` API for creating ranges over `<textarea>` and `<input>` values, enabling caret popup positioning and CSS Custom Highlight API usage on form controls. | [/opaque-range/](https://github.com/MicrosoftEdge/Demos/tree/main/opaque-range) | [OpaqueRange demo](https://microsoftedge.github.io/Demos/opaque-range/) |
| Page Colors Custom Scrollbars demo | Shows a custom, green scrollbar in a page that has custom colors. | [/page-colors-custom-scrollbars/](https://github.com/MicrosoftEdge/Demos/tree/main/page-colors-custom-scrollbars) | [Page Colors Custom Scrollbars demo](https://microsoftedge.github.io/Demos/page-colors-custom-scrollbars/) |
| Platform-provided behaviors for custom elements | Demonstrates `HTMLSubmitButtonBehavior` for custom elements: form submission, override properties, implicit submission, and accessibility. | [/platform-provided-behaviors-for-custom-elements/](https://github.com/MicrosoftEdge/Demos/tree/main/platform-provided-behaviors-for-custom-elements) | [Platform-provided behaviors for custom elements demo](https://microsoftedge.github.io/Demos/platform-provided-behaviors-for-custom-elements/) |
| Reference Target demos | Interactive demos of the Reference Target proposal, which allows ID references to cross shadow DOM boundaries. | [/reference-target/](https://github.com/MicrosoftEdge/Demos/tree/main/reference-target) | [Reference Target demos](https://microsoftedge.github.io/Demos/reference-target/) |
| Reader app | An article reader app used to demonstrate how to use various web APIs such as CSS Custom Highlight, `<selectmenu>`, EyeDropper, CSS and JSON modules, Scroll animation timeline, and Async Clipboard. | [/reader/](https://github.com/MicrosoftEdge/Demos/tree/main/reader) | [Reader](https://microsoftedge.github.io/Demos/reader/) demo |
| Scoped Custom Element Registries | Define custom elements scoped to a specific shadow roots, elements, or documents. | [/scoped-custom-element-registries/](https://github.com/MicrosoftEdge/Demos/tree/main/scoped-custom-element-registries) | [Scoped Custom Element Registries](https://microsoftedge.github.io/Demos/scoped-custom-element-registries/) demo |
Expand Down
38 changes: 38 additions & 0 deletions platform-provided-behaviors-for-custom-elements/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Platform-provided behaviors for custom elements

➡️ **[Open the demo](https://microsoftedge.github.io/Demos/platform-provided-behaviors-for-custom-elements/)** ⬅️

🔗 **Links:**
* Explainer: [Platform-provided behaviors for custom elements explainer](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/PlatformProvidedBehaviors/explainer.md)
* WHATWG issue: [#12150](https://github.com/whatwg/html/issues/12150)
* Spec PR: [whatwg/html#12409](https://github.com/whatwg/html/pull/12409)
* Chromium bug: [crbug.com/486928684](https://crbug.com/486928684)

## Overview

Platform-provided behaviors allow custom elements to adopt native HTML behaviors through `attachInternals({ behaviors: [...] })`. The first behavior, `HTMLSubmitButtonBehavior`, turns a custom element into a submit button that participates in form submission, contributes name/value pairs, supports form override attributes, triggers implicit submission, exposes the correct accessibility semantics, and responds to keyboard activation.

## Demos

- **Basic form submission**: A `<my-submit-button>` custom element with `HTMLSubmitButtonBehavior` submits a form the same way a native `<button type="submit">` does.
- **Form override properties**: Shows how `name`, `value`, `formAction`, `formMethod`, and `formEnctype` on the behavior instance let an `<override-submit-button>` custom element override the form's defaults.
- **Implicit submission**: Pressing Enter in a text field triggers implicit submission through an `<implicit-submit-button>` custom element.
- **Accessibility and keyboard activation**: An `<a11y-submit-button>` custom element gets `role="button"`, focusability, and Enter/Space activation with no extra code.

## Learn more

To learn more, read our [platform-provided behaviors for custom elements explainer](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/PlatformProvidedBehaviors/explainer.md).

## Test the feature

The feature is not enabled by default yet. To test the feature:

* Use Microsoft Edge 149 or later, or another Chromium-based browser with a matching version.
* In a new tab, go to the `about://flags` page.
* Search for the flag named **Experimental Web Platform features**.
* Enable the **Experimental Web Platform features** flag.
* Restart the browser.
Comment thread
captainbrosset marked this conversation as resolved.
Outdated

## Provide feedback

Comment on the [WHATWG issue](https://github.com/whatwg/html/issues/12150).
163 changes: 163 additions & 0 deletions platform-provided-behaviors-for-custom-elements/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Platform-provided behaviors for custom elements</title>
<link rel="icon" type="image/png" href="https://edgestatic.azureedge.net/welcome/static/favicon.png">
<link rel="stylesheet" href="style.css">
</head>

<body>
<h1>Platform-provided behaviors for custom elements</h1>
<p>
Platform-provided behaviors allow custom elements to adopt native HTML
behaviors through <code>attachInternals({ behaviors: [...] })</code>. The first
behavior, <code>HTMLSubmitButtonBehavior</code>, turns a custom element into a
submit button that participates in form submission, contributes name/value
pairs, supports form override attributes, triggers implicit submission,
exposes the correct accessibility semantics, and responds to keyboard
activation.
<a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/PlatformProvidedBehaviors/explainer.md">Read
the explainer</a>.
<a href="https://github.com/whatwg/html/issues/12150">Give feedback</a>.
</p>

<div id="feature-warning" class="warning" hidden>
<strong>⚠️ HTMLSubmitButtonBehavior is not supported in this browser.</strong>
Enable the <strong>Experimental Web Platform features</strong> flag at
<code>about://flags</code> in Microsoft Edge or another Chromium-based browser (version 149+).
</div>

<!-- Use Case 1: Basic Form Submission -->
<section class="demo-section">
<h2>Use case 1: Basic form submission</h2>
<p>
The <strong>Submit form</strong> button below is a <code>&lt;my-submit-button&gt;</code>
custom element with <code>HTMLSubmitButtonBehavior</code>, which makes it submit
forms just like a native <code>&lt;button type="submit"&gt;</code> element. Click
the custom submit button below, or press Enter while focused on a field, to
submit the form.
</p>
Comment thread
anaskim marked this conversation as resolved.

<form id="form-basic">
<div class="form-row">
<label for="basic-name">Name:</label>
<input type="text" id="basic-name" name="username" placeholder="Enter your name" value="Alice">
</div>
<div class="form-row">
<label for="basic-email">Email:</label>
<input type="email" id="basic-email" name="email" placeholder="Enter your email" value="alice@example.com">
</div>
<my-submit-button>Submit form</my-submit-button>
</form>
<div id="basic-output" class="output" hidden>
<h3>Form data submitted:</h3>
<pre id="basic-data"></pre>
</div>
<p class="info-text">
<a href="https://github.com/MicrosoftEdge/Demos/blob/main/platform-provided-behaviors-for-custom-elements/script.js#L11-L20">View source on GitHub</a>
</p>
</section>

<!-- Use Case 2: Form Override Properties -->
<section class="demo-section">
<h2>Use case 2: Form override properties</h2>
<p>
The two buttons below are <code>&lt;override-submit-button&gt;</code> custom elements.
Each one sets <code>name</code>, <code>value</code>, and <code>formMethod</code>
on its <code>HTMLSubmitButtonBehavior</code> instance to override the form's
default method and include its own name/value pair in the submission data.
</p>

<form id="form-overrides">
<div class="form-row">
<label for="override-message">Message:</label>
<input type="text" id="override-message" name="message" value="Hello, world!">
</div>
<div class="button-group">
<override-submit-button id="save-btn">
Save (POST)
</override-submit-button>
<override-submit-button id="draft-btn">
Save as draft (GET)
</override-submit-button>
</div>
</form>
<div id="overrides-output" class="output" hidden>
<h3>Submission details:</h3>
<pre id="overrides-data"></pre>
</div>
<p class="info-text">
<a href="https://github.com/MicrosoftEdge/Demos/blob/main/platform-provided-behaviors-for-custom-elements/script.js#L22-L45">View source on GitHub</a>
</p>
</section>

<!-- Use Case 3: Implicit Submission -->
<section class="demo-section">
<h2>Use case 3: Implicit submission</h2>
<p>
The <strong>Search</strong> button below is an <code>&lt;implicit-submit-button&gt;</code>
custom element. When a form contains a custom element with
<code>HTMLSubmitButtonBehavior</code>, pressing <kbd>Enter</kbd> in a text field
triggers implicit submission through that element, just like it would with a
native submit button. Try pressing <kbd>Enter</kbd> in the input below.
</p>

<form id="form-implicit">
<div class="form-row">
<label for="implicit-search">Search:</label>
<input type="text" id="implicit-search" name="query" placeholder="Type something and press Enter...">
</div>
<implicit-submit-button>Search</implicit-submit-button>
</form>
<div id="implicit-output" class="output" hidden>
<h3>Implicitly submitted:</h3>
<pre id="implicit-data"></pre>
</div>
<p class="info-text">
<a href="https://github.com/MicrosoftEdge/Demos/blob/main/platform-provided-behaviors-for-custom-elements/script.js#L47-L56">View source on GitHub</a>
</p>
</section>

<!-- Use Case 4: Accessibility -->
<section class="demo-section">
<h2>Use case 4: Accessibility and keyboard activation</h2>
<p>
The <strong>Accessible submit</strong> button below is an
<code>&lt;a11y-submit-button&gt;</code> custom element. Custom elements with
<code>HTMLSubmitButtonBehavior</code> automatically get:
</p>
<ul>
<li>Implicit ARIA <code>role="button"</code></li>
<li>Focusability (default <code>tabindex=0</code>)</li>
<li>Keyboard activation via <kbd>Enter</kbd> and <kbd>Space</kbd></li>
</ul>
<p>
Try using <kbd>Tab</kbd> to focus the button below, then press
<kbd>Enter</kbd> or <kbd>Space</kbd> to activate it.
</p>

<form id="form-a11y">
<div class="form-row">
<label for="a11y-data">Data:</label>
<input type="text" id="a11y-data" name="payload" value="keyboard-submitted">
</div>
<a11y-submit-button>Accessible submit</a11y-submit-button>
</form>
<div id="a11y-output" class="output" hidden>
<h3>Activated via keyboard:</h3>
<pre id="a11y-data-output"></pre>
</div>
<p id="a11y-info" class="info-text">
Inspect the custom element in DevTools. Its computed accessibility role is
<code>button</code>, with no explicit ARIA attributes needed.
<a href="https://github.com/MicrosoftEdge/Demos/blob/main/platform-provided-behaviors-for-custom-elements/script.js#L58-L67">View source on GitHub</a>
</p>
</section>

<script src="script.js"></script>
</body>

</html>
173 changes: 173 additions & 0 deletions platform-provided-behaviors-for-custom-elements/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// ---- Feature detection ----
const supportsBehaviors = typeof HTMLSubmitButtonBehavior !== "undefined";

if (!supportsBehaviors) {
document.getElementById("feature-warning").hidden = false;
}

// ---- Custom element definitions ----

if (supportsBehaviors) {
class MySubmitButton extends HTMLElement {
static formAssociated = true;

constructor() {
super();
const behavior = new HTMLSubmitButtonBehavior();
this.attachInternals({ behaviors: [behavior] });
}
}
customElements.define("my-submit-button", MySubmitButton);

class OverrideSubmitButton extends HTMLElement {
static formAssociated = true;
#behavior;

constructor() {
super();
this.#behavior = new HTMLSubmitButtonBehavior();
this.attachInternals({ behaviors: [this.#behavior] });
}

get behavior() { return this.#behavior; }
}
customElements.define("override-submit-button", OverrideSubmitButton);

// Set form override properties on the behavior instances.
const saveBtn = document.querySelector("#save-btn");
saveBtn.behavior.name = "action";
saveBtn.behavior.value = "save";
saveBtn.behavior.formMethod = "post";

const draftBtn = document.querySelector("#draft-btn");
draftBtn.behavior.name = "action";
draftBtn.behavior.value = "draft";
draftBtn.behavior.formMethod = "get";

class ImplicitSubmitButton extends HTMLElement {
static formAssociated = true;

constructor() {
super();
const behavior = new HTMLSubmitButtonBehavior();
this.attachInternals({ behaviors: [behavior] });
}
}
customElements.define("implicit-submit-button", ImplicitSubmitButton);

class A11ySubmitButton extends HTMLElement {
static formAssociated = true;

constructor() {
super();
const behavior = new HTMLSubmitButtonBehavior();
this.attachInternals({ behaviors: [behavior] });
}
}
customElements.define("a11y-submit-button", A11ySubmitButton);
}

// ===========================================================================
// Use Case 1: Basic Form Submission
// ===========================================================================

const formBasic = document.getElementById("form-basic");
const basicOutput = document.getElementById("basic-output");
const basicData = document.getElementById("basic-data");

formBasic.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(formBasic);
const entries = [...data.entries()];

const submitter = event.submitter;
let result = "Form entries:\n";
entries.forEach(([key, val]) => {
result += ` ${key} = ${val}\n`;
});
result += `\nSubmitter: <${submitter?.localName || "unknown"}>`;
if (submitter?.tagName && !submitter.tagName.includes("-")) {
result += " (native)";
} else if (submitter?.tagName) {
result += " (custom element)";
}

basicData.textContent = result;
basicOutput.hidden = false;
});

// ===========================================================================
// Use Case 2: Form Override Properties
// ===========================================================================

const formOverrides = document.getElementById("form-overrides");
const overridesOutput = document.getElementById("overrides-output");
const overridesData = document.getElementById("overrides-data");

formOverrides.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(formOverrides);
const entries = [...data.entries()];

const submitter = event.submitter;
let result = "Form entries:\n";
entries.forEach(([key, val]) => {
result += ` ${key} = ${val}\n`;
});

result += `\nSubmitter: <${submitter?.localName || "unknown"}>`;
result += `\nBehavior name: ${submitter?.behavior?.name || "(none)"}`;
result += `\nBehavior value: ${submitter?.behavior?.value || "(none)"}`;
result += `\nBehavior formMethod: ${submitter?.behavior?.formMethod || "(none)"}`;

overridesData.textContent = result;
overridesOutput.hidden = false;
});

// ===========================================================================
// Use Case 3: Implicit Submission
// ===========================================================================

const formImplicit = document.getElementById("form-implicit");
const implicitOutput = document.getElementById("implicit-output");
const implicitData = document.getElementById("implicit-data");

formImplicit.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(formImplicit);
const entries = [...data.entries()];

let result = "Form entries:\n";
entries.forEach(([key, val]) => {
result += ` ${key} = ${val}\n`;
});
result += `\nSubmitter: <${event.submitter?.localName || "unknown"}> (custom element)`;

implicitData.textContent = result;
implicitOutput.hidden = false;
});

// ===========================================================================
// Use Case 4: Accessibility and Keyboard Activation
// ===========================================================================

const formA11y = document.getElementById("form-a11y");
const a11yOutput = document.getElementById("a11y-output");
const a11yDataOutput = document.getElementById("a11y-data-output");

formA11y.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(formA11y);
const entries = [...data.entries()];

let result = "Form entries:\n";
entries.forEach(([key, val]) => {
result += ` ${key} = ${val}\n`;
});
result += `\nSubmitter: <${event.submitter?.localName || "unknown"}>`;
result += `\nSubmitter tabIndex: ${event.submitter?.tabIndex ?? "N/A"}`;

a11yDataOutput.textContent = result;
a11yOutput.hidden = false;
});

Loading