1. 4.13 Custom elements
      1. 4.13.1 Introduction
        1. 4.13.1.1 Creating an autonomous custom element
        2. 4.13.1.2 Creating a form-associated custom element
        3. 4.13.1.3 Creating a custom element with default accessible roles, states, and properties
        4. 4.13.1.4 Creating a customized built-in element
        5. 4.13.1.5 Drawbacks of autonomous custom elements
        6. 4.13.1.6 Upgrading elements after their creation
        7. 4.13.1.7 Exposing custom element states
      2. 4.13.2 Requirements for custom element constructors and reactions
      3. 4.13.3 Core concepts
      4. 4.13.4 The CustomElementRegistry interface
      5. 4.13.5 Custom element reactions
      6. 4.13.6 Element internals
        1. 4.13.6.1 Shadow root access
        2. 4.13.6.2 Form-associated custom elements
        3. 4.13.6.3 Accessibility semantics
        4. 4.13.6.4 Custom state pseudo-class

4.13 Custom elements

Using_custom_elements

Support in all current engines.

Firefox63+Safari10.1+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

4.13.1 Introduction

カスタム要素は、著者に完全に機能するDOM要素を独自に構築する方法を提供する。スクリプティングなどで事後に追加されたアプリケーション固有の動作とともに、著者は文書内で非標準要素を常に使用することができたが、そのような要素は歴史的に不適合であり、あまり機能しなかった。カスタム要素を定義することで、著者はパーサーに要素を正しく構成する方法と、そのクラスの要素が変更にどのように反応するかを通知できる。

カスタム要素は、(カスタム要素の定義のような)低レベルの著者公開拡張ポイントに関して、(HTMLの要素のような)既存のプラットフォーム機能を説明することによって、"プラットフォームを合理化する"ための大きな努力の一部である。今日、HTMLの既存要素の振る舞いを完全に説明することを妨げるカスタム要素(機能的および意味的な要素)の機能には多くの制限があるが、我々は時間の経過とともにこのギャップを縮小したいと思う。

4.13.1.1 Creating an autonomous custom element

自律カスタム要素を作成する方法を説明するために、国旗の小さなアイコンのレンダリングをカプセル化するカスタム要素を定義してみよう。私たちの目標は、次のように使用できるようにすることである:

<flag-icon country="nl"></flag-icon>

これを行うには、まずカスタム要素のクラスを宣言し、HTMLElementを拡張する:

class FlagIcon extends HTMLElement {
  constructor() {
    super();
    this._countryCode = null;
  }

  static observedAttributes = ["country"];

  attributeChangedCallback(name, oldValue, newValue) {
    // name will always be "country" due to observedAttributes
    this._countryCode = newValue;
    this._updateRendering();
  }
  connectedCallback() {
    this._updateRendering();
  }

  get country() {
    return this._countryCode;
  }
  set country(v) {
    this.setAttribute("country", v);
  }

  _updateRendering() {
    // Left as an exercise for the reader. But, you'll probably want to
    // check this.ownerDocument.defaultView to see if we've been
    // inserted into a document with a browsing context, and avoid
    // doing any work if not.
  }
}

次に、要素を定義するために次のクラスを使用して必要がある:

customElements.define("flag-icon", FlagIcon);

この時点で、上記のコードは動作する。flag-iconタグを見るたびに、パーサーはFlagIconクラスの新しいインスタンスを作成し、要素の内部状態を設定し、レンダリングを更新するために使用する(適切な場合)、新しいcountry属性についてコードに伝える。

DOM APIを使用してflag-icon要素を作成することもできる:

const flagIcon = document.createElement("flag-icon")
flagIcon.country = "jp"
document.body.appendChild(flagIcon)

最後に、カスタム要素コンストラクタ自体を使用することもできる。つまり、上記のコードは次のコードと同じである:

const flagIcon = new FlagIcon()
flagIcon.country = "jp"
document.body.appendChild(flagIcon)
4.13.1.2 Creating a form-associated custom element

Adding a static formAssociated property, with a true value, makes an autonomous custom element a form-associated custom element. The ElementInternals interface helps you to implement functions and properties common to form control elements.

class MyCheckbox extends HTMLElement {
  static formAssociated = true;
  static observedAttributes = ['checked'];

  constructor() {
    super();
    this._internals = this.attachInternals();
    this.addEventListener('click', this._onClick.bind(this));
  }

  get form() { return this._internals.form; }
  get name() { return this.getAttribute('name'); }
  get type() { return this.localName; }

  get checked() { return this.hasAttribute('checked'); }
  set checked(flag) { this.toggleAttribute('checked', Boolean(flag)); }

  attributeChangedCallback(name, oldValue, newValue) {
    // name will always be "checked" due to observedAttributes
    this._internals.setFormValue(this.checked ? 'on' : null);
  }

  _onClick(event) {
    this.checked = !this.checked;
  }
}
customElements.define('my-checkbox', MyCheckbox);

You can use the custom element my-checkbox like a built-in form-associated element. For example, putting it in form or label associates the my-checkbox element with them, and submitting the form will send data provided by my-checkbox implementation.

<form action="..." method="...">
  <label><my-checkbox name="agreed"></my-checkbox> I read the agreement.</label>
  <input type="submit">
</form>
4.13.1.3 Creating a custom element with default accessible roles, states, and properties

By using the appropriate properties of ElementInternals, your custom element can have default accessibility semantics. The following code expands our form-associated checkbox from the previous section to properly set its default role and checkedness, as viewed by accessibility technology:

class MyCheckbox extends HTMLElement {
  static formAssociated = true;
  static observedAttributes = ['checked'];

  constructor() {
    super();
    this._internals = this.attachInternals();
    this.addEventListener('click', this._onClick.bind(this));

    this._internals.role = 'checkbox';
    this._internals.ariaChecked = 'false';
  }

  get form() { return this._internals.form; }
  get name() { return this.getAttribute('name'); }
  get type() { return this.localName; }

  get checked() { return this.hasAttribute('checked'); }
  set checked(flag) { this.toggleAttribute('checked', Boolean(flag)); }

  attributeChangedCallback(name, oldValue, newValue) {
    // name will always be "checked" due to observedAttributes
    this._internals.setFormValue(this.checked ? 'on' : null);
    this._internals.ariaChecked = this.checked;
  }

  _onClick(event) {
    this.checked = !this.checked;
  }
}
customElements.define('my-checkbox', MyCheckbox);

Note that, like for built-in elements, these are only defaults, and can be overridden by the page author using the role and aria-* attributes:

<!-- This markup is non-conforming -->
<input type="checkbox" checked role="button" aria-checked="false">
<!-- This markup is probably not what the custom element author intended -->
<my-checkbox role="button" checked aria-checked="false">

Custom element authors are encouraged to state what aspects of their accessibility semantics are strong native semantics, i.e., should not be overridden by users of the custom element. In our example, the author of the my-checkbox element would state that its role and aria-checked values are strong native semantics, thus discouraging code such as the above.

4.13.1.4 Creating a customized built-in element

カスタマイズされた組み込み要素は、自律カスタム要素とはわずかに異なる定義があり、そして大きく異なる使われ方をする、独自の種類のカスタム要素である。この要素は、新しいカスタム機能でHTMLの既存要素を拡張することで、HTMLの既存要素由来の動作の再利用を可能にするために存在する。残念なことに、HTML要素の既存の動作の多くが自律カスタム要素を純粋に使用して複製できないため、これは重要である。代わりに、カスタマイズされた組み込み要素は、既存の要素にカスタム構築動作、ライフサイクルフック、およびプロトタイプチェーンをインストールを可能にし、既存の要素の上にこれらの機能を本質的に"混在"させる。

カスタマイズされた組み込み要素は、ユーザーエージェントや他のソフトウェアが要素のセマンティクスおよび動作を識別するために要素のローカル名として扱うので、自律カスタム要素とは異なる構文を必要とする。つまり、既存の動作の上に構築するカスタマイズされた組み込み要素の概念は、元のローカル名を保持する拡張要素に決定的に依存する。

この例において、plastic-buttonというカスタマイズされた組み込み要素を作成する。これは通常のボタンのように動作するが、クリックするたびにファンシーなアニメーション効果が追加される。今回はHTMLElementの代わりにHTMLButtonElementを拡張するが、前と同じようにクラスを定義することから始める:

class PlasticButton extends HTMLButtonElement {
  constructor() {
    super();

    this.addEventListener("click", () => {
      // Draw some fancy animation effects!
    });
  }
}

カスタム要素を定義する場合、extendsオプションも指定する必要がある:

customElements.define("plastic-button", PlasticButton, { extends: "button" });

一般に、拡張されている要素の名前は、同じインターフェイスを共有する要素(HTMLQuoteElementを共有するqblockquoteの両者など)を共有するため、拡張する要素インターフェイスを単に調べるだけでは判断することができない。

To construct our customized built-in element from parsed HTML source text, we use the is attribute on a button element:

<button is="plastic-button">Click Me!</button>

カスタマイズされた組み込み要素自律カスタム要素として使用しようとするとうまくいかない。つまり、<plastic-button>Click me?</plastic-button>は特別な動作をしないHTMLElementを作成するだけである。

プログラムでカスタマイズしたビルトイン要素を作成する必要がある場合、次のcreateElement()の形式を使用することができる:

const plasticButton = document.createElement("button", { is: "plastic-button" });
plasticButton.textContent = "Click me!";

前と同じように、コンストラクタも動作する:

const plasticButton2 = new PlasticButton();
console.log(plasticButton2.localName);  // will output "button"
console.assert(plasticButton2 instanceof PlasticButton);
console.assert(plasticButton2 instanceof HTMLButtonElement);

プログラムでカスタマイズした組み込み要素を作成する場合、明示的に設定されていないのでis属性はDOMに存在しない。ただし、シリアライズ時に出力に追加される

console.assert(!plasticButton.hasAttribute("is"));
console.log(plasticButton.outerHTML); // will output '<button is="plastic-button"></button>'

Regardless of how it is created, all of the ways in which button is special apply to such "plastic buttons" as well: their focus behavior, ability to participate in form submission, the disabled attribute, and so on.

カスタマイズされた組み込み要素は、有用なユーザーエージェントが提供する動作またはAPIを持つ既存のHTML要素を拡張を可能にするように設計されている。そのため、この仕様で定義される既存のHTML要素のみを拡張することができ、要素インターフェイスとしてHTMLUnknownElementを使用するように定義されたbgsoundblinkisindexkeygenmulticolnextid、またはspacerなどのレガシー要素は拡張できない。

この要件の1つの理由は将来の互換性である。もしカスタマイズされた組み込み要素が、たとえばcomboboxのような、現在不明の要素を拡張したものが定義されたなら、派生したカスタマイズされた組み込み要素のコンシューマーが、ユーザーエージェント提供の動作に興味を持たない基本要素に依存するようになるため、これはこの仕様が将来combobox要素を定義することを妨げるかもしれない。

4.13.1.5 Drawbacks of autonomous custom elements

以下で規定されているように、そして上記で触れたように、単にtaco-buttonという要素を定義して使用しても、そのような要素がボタンを表すことを意味しない。That is, tools such as web browsers, search engines, or accessibility technology will not automatically treat the resulting element as a button just based on its defined name.

自律カスタム要素を依然として使用する一方で、様々なユーザーに所望のボタンセマンティクスを伝えるためためには、複数の技術を用いる必要がある:

これらの点を念頭に置いて、(無効にする機能を含む)ボタンのセマンティクスを伝える責任を負ったフル機能のtaco-buttonは、次のようになる:

class TacoButton extends HTMLElement {
  static observedAttributes = ["disabled"];

  constructor() {
    super();
    this._internals = this.attachInternals();
    this._internals.role = "button";

    this.addEventListener("keydown", e => {
      if (e.code === "Enter" || e.code === "Space") {
        this.dispatchEvent(new PointerEvent("click", {
          bubbles: true,
          cancelable: true
        }));
      }
    });

    this.addEventListener("click", e => {
      if (this.disabled) {
        e.preventDefault();
        e.stopImmediatePropagation();
      }
    });

    this._observer = new MutationObserver(() => {
      this._internals.ariaLabel = this.textContent;
    });
  }

  connectedCallback() {
    this.setAttribute("tabindex", "0");

    this._observer.observe(this, {
      childList: true,
      characterData: true,
      subtree: true
    });
  }

  disconnectedCallback() {
    this._observer.disconnect();
  }

  get disabled() {
    return this.hasAttribute("disabled");
  }
  set disabled(flag) {
    this.toggleAttribute("disabled", Boolean(flag));
  }

  attributeChangedCallback(name, oldValue, newValue) {
    // name will always be "disabled" due to observedAttributes
    if (this.disabled) {
      this.removeAttribute("tabindex");
      this._internals.ariaDisabled = "true";
    } else {
      this.setAttribute("tabindex", "0");
      this._internals.ariaDisabled = "false";
    }
  }
}

Even with this rather-complicated element definition, the element is not a pleasure to use for consumers: it will be continually "sprouting" tabindex attributes of its own volition, and its choice of tabindex="0" focusability behavior may not match the button behavior on the current platform. This is because as of now there is no way to specify default focus behavior for custom elements, forcing the use of the tabindex attribute to do so (even though it is usually reserved for allowing the consumer to override default behavior).

対照的に、前節で示したように、単一のカスタマイズされた組み込み要素は、button要素のセマンティクスと動作を自動的に継承する。これらの動作を手動で実装する必要はない。一般に、HTMLの既存要素の上に構築される重要な動作やセマンティクスを持つ要素については、カスタマイズされた組み込み要素の開発、保守、および消費が容易になる。

4.13.1.6 Upgrading elements after their creation

Because element definition can occur at any time, a non-custom element could be created, and then later become a custom element after an appropriate definition is registered. We call this process "upgrading" the element, from a normal element into a custom element.

Upgrades enable scenarios where it may be preferable for custom element definitions to be registered after relevant elements have been initially created, such as by the parser. They allow progressive enhancement of the content in the custom element. For example, in the following HTML document the element definition for img-viewer is loaded asynchronously:

<!DOCTYPE html>
<html lang="en">
<title>Image viewer example</title>

<img-viewer filter="Kelvin">
  <img src="images/tree.jpg" alt="A beautiful tree towering over an empty savannah">
</img-viewer>

<script src="js/elements/img-viewer.js" async></script>

The definition for the img-viewer element here is loaded using a script element marked with the async attribute, placed after the <img-viewer> tag in the markup. While the script is loading, the img-viewer element will be treated as an undefined element, similar to a span. Once the script loads, it will define the img-viewer element, and the existing img-viewer element on the page will be upgraded, applying the custom element's definition (which presumably includes applying an image filter identified by the string "Kelvin", enhancing the image's visual appearance).


Note that upgrades only apply to elements in the document tree. (Formally, elements that are connected.) An element that is not inserted into a document will stay un-upgraded. An example illustrates this point:

<!DOCTYPE html>
<html lang="en">
<title>Upgrade edge-cases example</title>

<example-element></example-element>

<script>
  "use strict";

  const inDocument = document.querySelector("example-element");
  const outOfDocument = document.createElement("example-element");

  // Before the element definition, both are HTMLElement:
  console.assert(inDocument instanceof HTMLElement);
  console.assert(outOfDocument instanceof HTMLElement);

  class ExampleElement extends HTMLElement {}
  customElements.define("example-element", ExampleElement);

  // After element definition, the in-document element was upgraded:
  console.assert(inDocument instanceof ExampleElement);
  console.assert(!(outOfDocument instanceof ExampleElement));

  document.body.appendChild(outOfDocument);

  // Now that we've moved the element into the document, it too was upgraded:
  console.assert(outOfDocument instanceof ExampleElement);
</script>
4.13.1.7 Exposing custom element states

Built-in elements provided by user agents have certain states that can change over time depending on user interaction and other factors, and are exposed to web authors through pseudo-classes. For example, some form controls have the "invalid" state, which is exposed through the :invalid pseudo-class.

Like built-in elements, custom elements can have various states to be in too, and custom element authors want to expose these states in a similar fashion as the built-in elements.

This is done via the :state() pseudo-class. A custom element author can use the states property of ElementInternals to add and remove such custom states, which are then exposed as arguments to the :state() pseudo-class.

The following shows how :state() can be used to style a custom checkbox element. Assume that LabeledCheckbox doesn't expose its "checked" state via a content attribute.

<script>
class LabeledCheckbox extends HTMLElement {
  constructor() {
    super();
    this._internals = this.attachInternals();
    this.addEventListener('click', this._onClick.bind(this));

    const shadowRoot = this.attachShadow({mode: 'closed'});
    shadowRoot.innerHTML =
      `<style>
       :host::before {
         content: '[ ]';
         white-space: pre;
         font-family: monospace;
       }
       :host(:state(checked))::before { content: '[x]' }
       </style>
       <slot>Label</slot>`;
  }

  get checked() { return this._internals.states.has('checked'); }

  set checked(flag) {
    if (flag)
      this._internals.states.add('checked');
    else
      this._internals.states.delete('checked');
  }

  _onClick(event) {
    this.checked = !this.checked;
  }
}

customElements.define('labeled-checkbox', LabeledCheckbox);
</script>

<style>
labeled-checkbox { border: dashed red; }
labeled-checkbox:state(checked) { border: solid; }
</style>

<labeled-checkbox>You need to check this</labeled-checkbox>

Custom pseudo-classes can even target shadow parts. An extension of the above example shows this:

<script>
class QuestionBox extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({mode: 'closed'});
    shadowRoot.innerHTML =
      `<div><slot>Question</slot></div>
       <labeled-checkbox part='checkbox'>Yes</labeled-checkbox>`;
  }
}
customElements.define('question-box', QuestionBox);
</script>

<style>
question-box::part(checkbox) { color: red; }
question-box::part(checkbox):state(checked) { color: green; }
</style>

<question-box>Continue?</question-box>

4.13.2 Requirements for custom element constructors and reactions

When authoring custom element constructors, authors are bound by the following conformance requirements:

Several of these requirements are checked during element creation, either directly or indirectly, and failing to follow them will result in a custom element that cannot be instantiated by the parser or DOM APIs. This is true even if the work is done inside a constructor-initiated microtask, as a microtask checkpoint can occur immediately after construction.

When authoring custom element reactions, authors should avoid manipulating the node tree as this can lead to unexpected results.

An element's connectedCallback can be queued before the element is disconnected, but as the callback queue is still processed, it results in a connectedCallback for an element that is no longer connected:

class CParent extends HTMLElement {
  connectedCallback() {
    this.firstChild.remove();
  }
}
customElements.define("c-parent", CParent);

class CChild extends HTMLElement {
  connectedCallback() {
    console.log("CChild connectedCallback: isConnected =", this.isConnected);
  }
}
customElements.define("c-child", CChild);

const parent = new CParent(),
      child = new CChild();
parent.append(child);
document.body.append(parent);

// Logs:
// CChild connectedCallback: isConnected = false

4.13.3 Core concepts

A custom element is an element that is custom. Informally, this means that its constructor and prototype are defined by the author, instead of by the user agent. This author-supplied constructor function is called the custom element constructor.

Two distinct types of custom elements can be defined:

Global_attributes/is

Firefox63+SafariNoChrome67+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
  1. An autonomous custom element, which is defined with no extends option. These types of custom elements have a local name equal to their defined name.

  2. A customized built-in element, which is defined with an extends option. These types of custom elements have a local name equal to the value passed in their extends option, and their defined name is used as the value of the is attribute, which therefore must be a valid custom element name.

After a custom element is created, changing the value of the is attribute does not change the element's behavior.

Autonomous custom elements have the following element definition:

カテゴリー
フローコンテンツ
フレージングコンテンツ
パルパブルコンテンツ
For form-associated custom elements: Listed, labelable, submittable, and resettable form-associated element.
この要素を使用できるコンテキスト
フレージングコンテンツが期待される場所。
コンテンツモデル
透過的
コンテンツ属性
Global attributes, except the is attribute
form, for form-associated custom elements — Associates the element with a form element
disabled, for form-associated custom elements — Whether the form control is disabled
readonly, for form-associated custom elements — Affects willValidate, plus any behavior added by the custom element author
name, for form-associated custom elements — Name of the element to use for form submission and in the form.elements API
名前空間を持たないその他の属性(文参照)。
Accessibility considerations:
For form-associated custom elements: for authors; for implementers.
Otherwise: for authors; for implementers.
DOMインターフェイス
Supplied by the element's author (inherits from HTMLElement)

An autonomous custom element does not have any special meaning: it represents its children. A customized built-in element inherits the semantics of the element that it extends.

Any namespace-less attribute that is relevant to the element's functioning, as determined by the element's author, may be specified on an autonomous custom element, so long as the attribute name is XML-compatible and contains no ASCII upper alphas. The exception is the is attribute, which must not be specified on an autonomous custom element (and which will have no effect if it is).

Customized built-in elements follow the normal requirements for attributes, based on the elements they extend. To add custom attribute-based behavior, use data-* attributes.


An autonomous custom element is called a form-associated custom element if the element is associated with a custom element definition whose form-associated field is set to true.

The name attribute represents the form-associated custom element's name. The disabled attribute is used to make the form-associated custom element non-interactive and to prevent its submission value from being submitted. The form attribute is used to explicitly associate the form-associated custom element with its form owner.

The readonly attribute of form-associated custom elements specifies that the element is barred from constraint validation. User agents don't provide any other behavior for the attribute, but custom element authors should, where possible, use its presence to make their control non-editable in some appropriate fashion, similar to the behavior for the readonly attribute on built-in form controls.

Constraint validation: If the readonly attribute is specified on a form-associated custom element, the element is barred from constraint validation.

The reset algorithm for form-associated custom elements is to enqueue a custom element callback reaction with the element, callback name "formResetCallback", and an empty argument list.


A valid custom element name is a sequence of characters name that meets all of the following requirements:

These requirements ensure a number of goals for valid custom element names:

Apart from these restrictions, a large variety of names is allowed, to give maximum flexibility for use cases like <math-α> or <emotion-😍>.

4.13.4 The CustomElementRegistry interface

CustomElementRegistry

Support in all current engines.

Firefox63+Safari10.1+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

Custom element registries are associated with Window objects, instead of Document objects, since each custom element constructor inherits from the HTMLElement interface, and there is exactly one HTMLElement interface per Window object.

window.customElements.define(name, constructor)
Defines a new custom element, mapping the given name to the given constructor as an autonomous custom element.
window.customElements.define(name, constructor, { extends: baseLocalName })
Defines a new custom element, mapping the given name to the given constructor as a customized built-in element for the element type identified by the supplied baseLocalName. A "NotSupportedError" DOMException will be thrown upon trying to extend a custom element or an unknown element.
window.customElements.get(name)
Retrieves the custom element constructor defined for the given name. Returns undefined if there is no custom element definition with the given name.
window.customElements.getName(constructor)

CustomElementRegistry/getName

Firefox116+Safari🔰 preview+Chrome117+
Opera?Edge117+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
Retrieves the given name for a custom element defined for the given constructor. Returns null if there is no custom element definition with the given constructor.
window.customElements.whenDefined(name)
Returns a promise that will be fulfilled with the custom element's constructor when a custom element becomes defined with the given name. (If such a custom element is already defined, the returned promise will be immediately fulfilled.) Returns a promise rejected with a "SyntaxError" DOMException if not given a valid custom element name.
window.customElements.upgrade(root)
Tries to upgrade all shadow-including inclusive descendant elements of root, even if they are not connected.

Element definition is a process of adding a custom element definition to the CustomElementRegistry. This is accomplished by the define() method.

The whenDefined() method can be used to avoid performing an action until all appropriate custom elements are defined. In this example, we combine it with the :defined pseudo-class to hide a dynamically-loaded article's contents until we're sure that all of the autonomous custom elements it uses are defined.

articleContainer.hidden = true;

fetch(articleURL)
  .then(response => response.text())
  .then(text => {
    articleContainer.innerHTML = text;

    return Promise.all(
      [...articleContainer.querySelectorAll(":not(:defined)")]
        .map(el => customElements.whenDefined(el.localName))
    );
  })
  .then(() => {
    articleContainer.hidden = false;
  });

The upgrade() method allows upgrading of elements at will. Normally elements are automatically upgraded when they become connected, but this method can be used if you need to upgrade before you're ready to connect the element.

const el = document.createElement("spider-man");

class SpiderMan extends HTMLElement {}
customElements.define("spider-man", SpiderMan);

console.assert(!(el instanceof SpiderMan)); // not yet upgraded

customElements.upgrade(el);
console.assert(el instanceof SpiderMan);    // upgraded!

4.13.5 Custom element reactions

A custom element possesses the ability to respond to certain occurrences by running author code:

We call these reactions collectively custom element reactions.

The way in which custom element reactions are invoked is done with special care, to avoid running author code during the middle of delicate operations. Effectively, they are delayed until "just before returning to user script". This means that for most purposes they appear to execute synchronously, but in the case of complicated composite operations (like cloning, or range manipulation), they will instead be delayed until after all the relevant user agent processing steps have completed, and then run together as a batch.

It is guaranteed that custom element reactions always are invoked in the same order as their triggering actions, at least within the local context of a single custom element. (Because custom element reaction code can perform its own mutations, it is not possible to give a global ordering guarantee across multiple elements.)

4.13.6 Element internals

Certain capabilities are meant to be available to a custom element author, but not to a custom element consumer. These are provided by the element.attachInternals() method, which returns an instance of ElementInternals. The properties and methods of ElementInternals allow control over internal features which the user agent provides to all elements.

element.attachInternals()

HTMLElement/attachInternals

Support in all current engines.

Firefox93+Safari16.4+Chrome77+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

Returns an ElementInternals object targeting the custom element element. Throws an exception if element is not a custom element, if the "internals" feature was disabled as part of the element definition, or if it is called twice on the same element.

4.13.6.1 Shadow root access
internals.shadowRoot

Returns the ShadowRoot for internals's target element, if the target element is a shadow host, or null otherwise.

4.13.6.2 Form-associated custom elements
internals.setFormValue(value)

Sets both the state and submission value of internals's target element to value.

If value is null, the element won't participate in form submission.

internals.setFormValue(value, state)

Sets the submission value of internals's target element to value, and its state to state.

If value is null, the element won't participate in form submission.

internals.form

Returns the form owner of internals's target element.

internals.setValidity(flags, message [, anchor ])

Marks internals's target element as suffering from the constraints indicated by the flags argument, and sets the element's validation message to message. If anchor is specified, the user agent might use it to indicate problems with the constraints of internals's target element when the form owner is validated interactively or reportValidity() is called.

internals.setValidity({})

Marks internals's target element as satisfying its constraints.

internals.willValidate

Returns true if internals's target element will be validated when the form is submitted; false otherwise.

internals.validity

Returns the ValidityState object for internals's target element.

internals.validationMessage

Returns the error message that would be shown to the user if internals's target element was to be checked for validity.

valid = internals.checkValidity()

Returns true if internals's target element has no validity problems; false otherwise. Fires an invalid event at the element in the latter case.

valid = internals.reportValidity()

Returns true if internals's target element has no validity problems; otherwise, returns false, fires an invalid event at the element, and (if the event isn't canceled) reports the problem to the user.

internals.labels

Returns a NodeList of all the label elements that internals's target element is associated with.

Each form-associated custom element has submission value. It is used to provide one or more entries on form submission. The initial value of submission value is null, and submission value can be null, a string, a File, or a list of entries.

Each form-associated custom element has state. It is information with which the user agent can restore a user's input for the element. The initial value of state is null, and state can be null, a string, a File, or a list of entries.

The setFormValue() method is used by the custom element author to set the element's submission value and state, thus communicating these to the user agent.

When the user agent believes it is a good idea to restore a form-associated custom element's state, for example after navigation or restarting the user agent, they may enqueue a custom element callback reaction with that element, callback name "formStateRestoreCallback", an argument list containing the state to be restored, and "restore".

If the user agent has a form-filling assist feature, then when the feature is invoked, it may enqueue a custom element callback reaction with a form-associated custom element, callback name "formStateRestoreCallback", an argument list containing the state value determined by history of state value and some heuristics, and "autocomplete".

In general, the state is information specified by a user, and the submission value is a value after canonicalization or sanitization, suitable for submission to the server. The following examples makes this concrete:

Suppose that we have a form-associated custom element which asks a user to specify a date. The user specifies "3/15/2019", but the control wishes to submit "2019-03-15" to the server. "3/15/2019" would be a state of the element, and "2019-03-15" would be a submission value.

Suppose you develop a custom element emulating a the behavior of the existing checkbox input type. Its submission value would be the value of its value content attribute, or the string "on". Its state would be one of "checked", "unchecked", "checked/indeterminate", or "unchecked/indeterminate".

4.13.6.3 Accessibility semantics
internals.role [ = value ]

Sets or retrieves the default ARIA role for internals's target element, which will be used unless the page author overrides it using the role attribute.

internals.aria* [ = value ]

Sets or retrieves various default ARIA states or property values for internals's target element, which will be used unless the page author overrides them using the aria-* attributes.

By using the role and aria* properties of ElementInternals, custom element authors can set default accessibile roles, states, and property values for their custom element, similar to how native elements behave. See the example above for more details.

4.13.6.4 Custom state pseudo-class
internals.states.add(value)

Adds the string value to the element's states set to be exposed as a pseudo-class.

internals.states.has(value)

Returns true if value is in the element's states set, otherwise false.

internals.states.delete(value)

If the element's states set has value, then it will be removed and true will be returned. Otherwise, false will be returned.

internals.states.clear()

Removes all values from the element's states set.

for (const stateName of internals.states)
for (const stateName of internals.states.entries())
for (const stateName of internals.states.keys())
for (const stateName of internals.states.values())

Iterates over all values in the element's states set.

internals.states.forEach(callback)

Iterates over all values in the element's states set by calling callback once for each value.

internals.states.size

Returns the number of values in the element's states set.

The states set can expose boolean states represented by existence/non-existence of string values. If an author wants to expose a state which can have three values, it can be converted to three exclusive boolean states. For example, a state called readyState with "loading", "interactive", and "complete" values can be mapped to three exclusive boolean states, "loading", "interactive", and "complete":

// Change the readyState from anything to "complete".
this._readyState = "complete";
this._internals.states.delete("loading");
this._internals.states.delete("interactive");
this._internals.states.add("complete");