Skip to content
Open
Show file tree
Hide file tree
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
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.

## 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