This specification describes the method for enabling the author to define and use new types of DOM elements in a document. It will eventually be upstreamed into [[!WHATWG-DOM]], [[!HTML]], and [[!WEBIDL]]. Sections which explicitly modify existing parts of these specifications are denoted with "DOM+", "HTML+", or "Web IDL+" in their titles.

Any point, at which a conforming UA must make decisions about the state or reaction to the state of the conceptual model, is captured as algorithm. The algorithms are defined in terms of processing equivalence. The processing equivalence is a constraint imposed on the algorithm implementors, requiring the output of the both UA-implemented and the specified algorithm to be exactly the same for all inputs.

The IDL fragments in this specification must be interpreted as required for conforming IDL fragments, as described in the Web IDL specification [[!WEBIDL]].

HTML+: Custom elements

This section should be inserted into the section The elements of HTML, probably as a subsection following "Scripting" and before "Common idioms without dedicated elements".

Introduction

Custom elements provide a way for authors to build their own fully-featured DOM elements. Although authors could always use non-standard tag names in their documents, with application-specific behavior added after the fact by scripting or similar, such elements have historically been non-conforming and not very functional. By defining a custom element, authors can inform the parser how to properly construct an element and how elements of that class should react to changes.

Custom elements are part of a larger effort to "rationalize the platform," by explaining existing platform features (like the elements of HTML) in terms of lower-level author-exposed extensibility points (like custom element definition). Although today there are many limitations on the capabilities of custom elements—both functionally and semantically—that prevent them from fully explaining the behaviors of HTML's existing elements, we hope to shrink this gap over time.

Creating a custom tag

For the purposes of illustrating how to create a custom tag, let's define a custom element that encapsulates rendering a small icon for a country flag. Our goal is to be able to use it like so:

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

To do this, we first declare a class for the custom element, extending HTMLElement:

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

  static get observedAttributes() { return ["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.
  }
}

We then need to use this class to define the element:

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

At this point, our above code will work! The parser, whenever it sees the flag-icon tag, will construct a new instance of our FlagIcon class, and tell our code about its new country attribute, which we then use to set the element's internal state and update its rendering (when appropriate).

You can also create flag-icon elements directly, using the DOM API:

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

Finally, we can also use the custom element constructor itself. That is, the above code is equivalent to:

const flagIcon = new FlagIcon()
flagIcon.country = "jp"
document.body.appendChild(flagIcon)

Creating a type extension

Type extensions are a distinct kind of custom element, which are defined slightly differently and used very differently. They exist to allow reuse of behaviors from the existing elements of HTML, by extending those elements with new custom functionality. This is important since many of the existing behaviors of HTML elements can unfortunately not be duplicated by using purely custom tags. Instead, type extensions allow the installation of custom construction behavior, lifecycle hooks, and prototype chain onto onto existing elements, essentially "mixing in" these capabilities on top of the already-existing element.

Type extensions require a distinct syntax from custom tags because user agents and other software key off an element's local name in order to identify the element's basic nature. That is, the concept of type extensions building on top of existing behavior depends crucially on the extended elements retaining their original local name.

In this example, we'll be creating a type extension named plastic-button, which behaves like a normal button but gets fancy animation effects added whenever you click on it. We start by defining a class, just like before, although this time we extend HTMLButtonElement instead of HTMLElement:

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

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

When defining our custom element, we have to also specify the extends option:

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

In general, the name of the element being extended cannot be determined simply by looking at what element interface it extends, as many elements share the same interface (such as q and blockquote both sharing HTMLQuoteElement).

To use our type extension, we use the is attribute on a button element:

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

Trying to use a type extension as a custom tag will not work; that is, <plastic-button>Click me?</plastic-button> will simply create a HTMLUnknownElement with no special behavior.

If you need to create a type extension programmatically, you can use the following form of createElement:

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

And as before, the constructor will also work:

const plasticButton2 = new PlasticButton()
console.log(plasticButton2.localName);          // will output "button"
console.log(plasticButton2.getAttribute("is")); // will output "plastic-button"

Notably, all the 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.

Drawbacks of custom tags

As specified below, and alluded to above, simply defining and using an element called taco-button does not mean that such elements represent buttons. 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.

To convey the desired button semantics to a variety of users, while still using a custom tag, a number of techniques would need to be employed:

  • The addition of the tabindex attribute would make the taco-button interactive content, thus making it focusable. Note that if the taco-button were to become logically disabled, the tabindex attribute would need to be removed.
  • The addition of various ARIA attributes helps convey semantics to accessibility technology. For example, setting the role attribute to button will convey the semantics that this is a button, enabling users to successfully interact with the control using usual button-like interactions in their accessibility technology. Setting the aria-label attribute is necessary to give the button an accessible name, instead of having accessibility technology traverse its child text nodes and announce them. And setting aria-disabled to "true" when the button is logically disabled conveys to accessibility technology the button's disabled state.
  • The addition of event handlers to handle commonly-expected button behaviors helps convey the semantics of the button to Web browser users. In this case, the most relevant event handler would be one that proxies appropriate keydown events to become click events, so that you can activate the button both with keyboard and by clicking.
  • In addition to the default visual styling provided for taco-button elements, the visual styling will also need to be updated to reflect changes in logical state, such as becoming disabled; that is, whatever stylesheet has rules for taco-button will also need to have rules for taco-button[disabled].

With these points in mind, a full-featured taco-button that took on the responsibility of conveying button semantics (including the ability to be disabled) might look something like this:

class TacoButton extends HTMLElement {
  static get observedAttributes() { return ["disabled"]; }

  constructor() {
    super();

    this.addEventListener("keydown", e => {
      if (e.keyCode === 32 || e.keyCode === 13) {
        this.dispatchEvent(new MouseEvent("click", {
          bubbles: true,
          cancelable: true
        }));
      }
    });

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

    this._observer = new MutationObserver(() => {
      this.setAttribute("aria-label", this.textContent);
    });
  }

  attachedCallback() {
    this.setAttribute("role", "button");
    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(v) {
    if (v) {
      this.setAttribute("disabled", "");
    } else {
      this.removeAttribute("disabled");
    }
  }

  attributeChangedCallback() {
    // only is called for the disabled attribute due to observedAttributes
    if (this.disabled) {
      this.setAttribute("tabindex", "-1");
      this.setAttribute("aria-disabled", "true");
    } else {
      this.setAttribute("tabindex", "0");
      this.setAttribute("aria-disabled", "false");
    }
  }
}

Even with this rather-complicated element definition, the element is not a pleasure to use for consumers: it will be continually "sprouting" tabindex and aria-* attributes of its own volition. This is because as of now there is no way to specify default accessibility semantics or focus behavior for custom elements, forcing the use of these attributes to do so (even though they are usually reserved for allowing the consumer to override default behavior).

In contrast, a simple type extension, as shown in the previous section, would automatically inherit the semantics and behavior of the button element, with no need to implement these behaviors manually. In general, for any element with nontrivial behavior and semantics which builds on top of existing elements of HTML, type extensions will be easier to develop, maintain, and consume.

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 has 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>
<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 in a shadow-including document.) An element that is not inserted into a document will stay un-upgraded. An example illustrates this point:

<!DOCTYPE html>
<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>

Requirements for custom element constructors

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.

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:

  1. A custom tag, which is defined with no extends option. These types of custom elements have local name equal to their defined name.
  2. A type extension, which is defined with an extends option. These types of custom elements have local name equal to the value passed in their extends option, and their defined name is used as the value of the is attribute.

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

Custom tags have the following element definition:

Categories:
Flow content.
Phrasing content.
Palpable content.
Contexts in which custom elements can be used:
Where phrasing content is expected.
Content model:
Transparent.
Tag omission in text/html:
Neither tag is omissible.
Content attributes:
Global attributes
Any attributes relevant to the element's functioning, as defined by the author
DOM interface:
Supplied by the author (inherits from HTMLElement).

A custom tag does not have any special meaning: it represents its children. A type extension inherits the semantics of the element that it extends.

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:

  • They start with a lowercase ASCII letter, ensuring that the HTML parser will treat them as tags instead of as text.
  • They do not contain any uppercase ASCII letters, ensuring that the user agent can always treat HTML elements ASCII-case-insensitively.
  • They contain a hyphen, used for namespacing and to ensure forward compatibility (since no elements will be added to HTML, SVG, or MathML with hyphen-containing tag names in the future).
  • They can always be created with document.createElement and document.createElementNS, which have restrictions that go beyond the parser's.

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

A custom element definition describes a custom element and consists of:

Each custom element definition's construction stack is manipulated by the upgrade an element algorithm and the HTMLElement constructor. Each entry in the list will be either an element or an already constructed marker.

Each custom element definition's lifecycle callbacks is a map, whose three keys are the strings "connectedCallback", "disconnectedCallback", and "attributeChangedCallback". The corresponding values are either a JavaScript function object, or undefined. By default the value of each entry is undefined.

To look up a custom element definition, given a document, namespace, localName, and is, perform the following steps. They will return either a custom element definition or null:

  1. If namespace is not the HTML namespace, return null.
  2. If document does not have a browsing context, return null.
  3. Let registry be document's associated Window's CustomElementsRegistry object.
  4. If there is custom element definition in registry with name and local name both equal to localName, return that custom element definition.
  5. If there is a custom element definition in registry with name equal to is and local name equal to localName, return that custom element definition.
  6. Return null.

The CustomElementsRegistry interface

Each Window object is associated with a unique instance of a CustomElementsRegistry object, allocated when the Window object is created.

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.

The customElements attribute of the Window interface must return the CustomElementsRegistry object for that Window object.

interface CustomElementsRegistry {
  void define(DOMString name, Function constructor, optional ElementRegistrationOptions options);
};

dictionary ElementRegistrationOptions {
  DOMString extends;
};
window . customElements . define(name, constructor)
Defines a new custom element, mapping the given name to the given constructor as a custom tag.
window . customElements . define(name, constructor, { extends: baseTagName })
Defines a new custom element, mapping the given name to the given constructor as a type extension for the supplied base tag. A NotSupportedError will be thrown upon trying to extend a custom element or an unknown element.

Every CustomElementsRegistry has a set of custom element definitions, initially empty. In general, algorithms in this specification look up elements in the registry by any of name, local name, or constructor.

A CustomElementsRegistry's list of defined local names is the list containing all of the local names of the custom element definitions in the registry.

Element definition is a process of adding a custom element definition to the CustomElementsRegistry. This is accomplished by the define method. When invoked, the define(name, constructor, options) method must run these steps:

  1. If IsConstructor(constructor) is false, throw a TypeError and abort these steps.
  2. If name is not a valid custom element name, throw a SyntaxError and abort these steps.
  3. If this CustomElementsRegistry contains an entry with name name, throw a NotSupportedError and abort these steps.
  4. If this CustomElementsRegistry contains an entry with constructor constructor, throw a NotSupportedError and abort these steps.
  5. Let localName be name.
  6. Let extends be the value of the extends member of options, or null if no such member exists.
  7. If extends is not null:

    1. If extends is a valid custom element name, or if the element interface for extends and the HTML namespace is HTMLUnknownElement, throw a NotSupportedError and abort these steps.
    2. Set localName to extends.
  8. Let observedAttributesIterable be Get(constructor, "observedAttributes"). Rethrow any exceptions.
  9. If observedAttributesIterable is undefined, let observedAttributes be an empty sequence<DOMString>. Otherwise, let observedAttributes be the result of converting observedAttributesIterable to a sequence<DOMString>. Rethrow any exceptions.
  10. Let prototype be Get(constructor, "prototype"). Rethrow any exceptions.
  11. If Type(prototype) is not Object, throw a TypeError exception.
  12. Let connectedCallback be Get(prototype, "connectedCallback"). Rethrow any exceptions.
  13. If connectedCallback is not undefined, and IsCallable(connectedCallback) is false, throw a TypeError exception.
  14. Let disconnectedCallback be Get(prototype, "disconnectedCallback"). Rethrow any exceptions.
  15. If disconnectedCallback is not undefined, and IsCallable(disconnectedCallback) is false, throw a TypeError exception.
  16. Let attributeChangedCallback be Get(prototype, "attributeChangedCallback"). Rethrow any exceptions.
  17. If attributeChangedCallback is not undefined, and IsCallable(attributeChangedCallback) is false, throw a TypeError exception.
  18. Let definition be a new custom element definition with name name, local name localName, constructor constructor, prototype prototype, observed attributes observedAttributes, and lifecycle callbacks connectedCallback, disconnectedCallback, and attributeChangedCallback (stored by their corresponding name).
  19. Add definition to this CustomElementsRegistry.

Upgrades

To upgrade an element, given as input a custom element definition definition and an element element, run the following steps:

  1. Add element to the end of definition's construction stack.
  2. Let C be definition's constructor.
  3. Let constructResult be Construct(C).
  4. Remove element from the end of definition's construction stack.
  5. If constructResult is an abrupt completion, return constructResult (i.e., re-throw the exception).
  6. If SameValue(constructResult.[[\value]], element) is false, throw a InvalidStateError and terminate these steps.

    This can occur if C constructs another instance of the same custom element before calling super(), or if C uses JavaScript's return-override feature to return an arbitrary object from the constructor.

  7. Set element's custom element state to "customized".
  8. For each attribute in element's attribute list, in order, enqueue a custom element callback reaction with element, callback name "attributeChangedCallback", and an argument list containing attribute's local name, null, attribute's value, and attribute's namespace.
  9. If element is currently in a shadow-including document, enqueue a custom element callback reaction with element, callback name "connectedCallback", and an empty argument list.

To try to upgrade an element, given as input an element element, run the following steps:

  1. Let document be element's node document.
  2. Let is be the value of the attribute in element's attribute list whose qualified name is "is", if any such attribute exists, or null otherwise.
  3. Let definition be the result of looking up a custom element definition given document, element's namespace, element's local name, and is.
  4. If definition is not null, enqueue a custom element upgrade reaction given element and definition.

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.

Additionally, the precise ordering of these reactions is managed via a somewhat-complicated stack-of-queues system, described below. The intention behind this system is to guarantee 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.)


Each unit of related similar-origin browsing contexts has a custom element reactions stack, which is initially empty. Each item in the stack is an element queue, which is initially empty as well; the element queue at the top of the stack is called the current element queue. Each item in an element queue is an element. (The elements are not necessarily custom yet, since this queue is used for upgrades as well.)

All elements have an associated custom element reaction queue, initially empty. Each item in the custom element reaction queue is of one of two types:

This is all summarized in the following schematic diagram:

A custom elements reaction stack consists of a stack of element queues. Zooming in on a particular queue, we see that it contains a number of elements (in our example, <x-a>, then <x-b>, then <x-c>). Any particular element in the queue then has a custom element reaction queue. Zooming in on the custom element reaction queue, we see that it contains a variety of queued-up reactions (in our example, upgrade, then attribute changed, then another attribute changed, then connected).

To enqueue a custom element callback reaction, given a custom element element, a callback name callbackName, and a list of arguments args, run the following steps:

  1. Let document be element's node document.
  2. If document does not have a browsing context, abort these steps.
  3. Let registry be document's associated Window's CustomElementsRegistry object.
  4. Let definition be the the entry in registry with name equal to element's local name.

    This algorithm can only be called when such a definition exists.

  5. Let callback be the value of the entry in definition's lifecycle callbacks with key callbackName.
  6. If callback is undefined, abort these steps.
  7. If callbackName is "attributeChangedCallback":

    1. Let attributeName be the the first element of args.
    2. If definition's observed attributes does not contain attributeName, abort these steps.
  8. Add a new callback reaction to element's custom element reaction queue, with callback function callback and arguments args.
  9. Add element to the current element queue.

To enqueue a custom element upgrade reaction, given an element element and custom element definition definition, run the following steps:

  1. Add a new upgrade reaction to element's custom element reaction queue, with custom element definition definition.
  2. Add element to the current element queue.

To invoke custom element reactions in an element queue queue, run the following steps:

  1. For each custom element element in queue:

    1. Let reactions be element's custom element reaction queue.
    2. Repeat until reactions is empty:

      1. Remove the first element of reactions, and let reaction be that element. Switch on reaction's type:

        upgrade reaction
        Upgrade element using reaction's custom element definition.
        callback reaction
        Invoke reaction's callback function with reaction's arguments, and with element as the callback this value.

        If this throws any exception, report the exception.


The following needs to be formalized more; see #186.

Any time a script calls a method, reads or sets a property that is implemented by the user agent, the following actions MUST occur:

As described, these actions wrap every user agent-implemented method or property accessor. The intended effect is that any lifecycle callbacks, enqueued as a result of running these methods or accessors are invoked prior to returning control back to script. If a method or accessor is known to never enqueue a custom element action, the user agent could choose not to wrap it as a performance optimization.

Miscellaneous patches

HTML+: The Window object

HTML's Window object definition must be extended as follows:

partial interface Window {
    readonly attribute CustomElementsRegistry customElements;
};

As is conventional for HTML, the actual definition of this property is elsewhere in the specification (cf. location and history). We have it above in the section "The CustomElementsRegistry interface".

HTML+: Element interfaces

HTML currently does not do a great job of defining DOM's element interface concept. There is a definition hidden inside the parser, but it isn't explicit that this also covers other element creation cases. We should create a section that is more explicit, and it should roughly contain the following (including the note afterward):

The element interface for an element with name name in the HTML namespace is determined as follows:

  1. If this specification defines an interface appropriate for the element type corresponding to the tag name name, return that interface.
  2. If other applicable specifications define such an appropriate interface for name, return the interface they define.
  3. If name is a valid custom element name, return HTMLElement.
  4. Otherwise, return HTMLUnknownElement.

The use of HTMLElement instead of HTMLUnknownElement in the case of valid custom element names is done to ensure that any potential future upgrades only cause a linear transition of the element's prototype chain, from HTMLElement to a subclass, instead of a lateral one, from HTMLUnknownElement to an unrelated subclass.

HTML+: Pseudo-classes

The following should be added to HTML's pseudo-classes section.

:defined
The :defined pseudo-class must match any element that is defined.

HTML+: The HTMLElement constructor

The HTMLElement interface gains following annotation:

[Constructor]

We then add the following definition:

The HTMLElement constructor, when invoked, must perform the following steps:
  1. Let realm be the result of GetFunctionRealm(the currently executing HTMLElement function).
  2. Let registry be realm's [[\GlobalObject]]'s CustomElementsRegistry object.
  3. Let definition be the entry in registry with constructor equal to NewTarget. If there is no such definition, throw a TypeError and abort these steps.
  4. If definition's construction stack is empty, perform the following substeps:

    1. Let localName be the definition's local name.
    2. Return a new element that implements HTMLElement, with no attributes, namespace set to the HTML namespace, local name set to localName, and node document set to document.

    This occurs when author script constructs a new custom element directly, e.g. via new MyCustomElement().

  5. Let instance be the last entry in definition's construction stack.
  6. If instance is an already constructed marker, throw an InvalidStateError and abort these steps.

    This can occur when the author code inside the custom element constructor invokes super() multiple times.

  7. Let prototype be definition's prototype.
  8. Perform element.[[\SetPrototypeOf]](prototype). Rethrow any exceptions.
  9. Replace the last entry in definition's construction stack with an already constructed marker.
  10. Return instance.

    This step is normally reached when upgrading a custom element; the existing element is returned, so that the super() call inside the custom element constructor assigns that existing element to this.

For now, the HTMLElement constructor cannot be invoked directly. It only works when used via a super() call inside a custom element constructor.

HTML+: Parsing HTML documents

The create an element for a token algorithm should be adjusted by replacing step 1 with the following steps, and adjusting further steps to refer to element instead of "the element" or "the newly created element".

  1. Let document be intended parent's node document.
  2. Let local name be the tag name of the token.
  3. Let is be the value of the "is" attribute in the given token, if such an attribute exists, or null otherwise.
  4. Let definition be the result of looking up a custom element definition given document, given namespace, local name, and is.
  5. If definition is non-null and the parser was not originally created for the HTML fragment parsing algorithm, let will execute script be true. Otherwise, let it be false.
  6. If will execute script is true:

    1. Increment the parser's script nesting level.
    2. Set the parser pause flag to true.
    3. If the JavaScript execution context stack is empty, perform a microtask checkpoint.
    4. Push a new element queue onto the custom element reactions stack.
  7. Let element be the result of creating an element given document, localName, null, given namespace, and is. If will execute script is true, set the synchronous custom elements flag; otherwise, leave it unset.

    This will cause custom element constructors to run, if will execute script is true. However, even if this causes new characters to be inserted into the tokenizer, the parser will not be executed reentrantly, since the parser pause flag is true. Similarly, blowing away the document is not possible, since the script nesting level is greater than zero.

    If this step throws an exception, report the exception, and let element be instead a new element that implements HTMLUnknownElement, with no attributes, namespace set to given namespace, namespace prefix set to null, custom element state "undefined", and node document set to document.

  8. Append each attribute in the given token to element.

    This can enqueue a custom element callback reaction for the attributeChangedCallback, which might run immediately (in the next step).

    Even though the "is" attribute governs the creation of a type extension, it is not present during the execution of the relevant custom element constructor; it is appended in this step, along with all other attributes.

  9. If will execute script is true:

    1. Let queue be the result of popping the current element queue from the custom element reactions stack. (This will be the same element queue as was pushed above.)
    2. Invoke custom element reactions in queue.
    3. Decrement the script nesting level by one.
    4. If the parser's script nesting level is zero, then set the parser pause flag to false.

HTML+: Parsing XHTML documents

It turns out there's not actually a spec for the XML parser. Awesome! (Not actually awesome.) The HTML Standard has a nice vague paragraph about "This Document must then be populated with DOM nodes that represent the tree structure of the input passed..." which should get something like the following inserted (probably by inserting it after the first sentence, then splitting the mutation events/observers prose into a new paragraph).

When creating DOM nodes representing elements, the create an element for a token algorithm or some equivalent that operates on appropriate XML datastructures must be used, to ensure the proper element interfaces are created and that custom elements are set up correctly.

HTML+: Content Model Updates

HTML has several sections that need to be updated when introducing a new element, or in this case class of elements. Those are:

DOM+: Elements

The Element interface section will need to be modified as follows. The paragraph listing the values associated with an element should be modified to read as follows, including the note afterward:

Elements have an associated namespace, namespace prefix, local name, and custom element state. When an element is created, all of these values are initialized.

An element's custom element state is one of "undefined", "uncustomized", or "custom". An element whose custom element state is either "uncustomized" or "custom" is said to be defined. An element whose custom element state is "custom" is said to be custom.

Whether or not an element is defined is used to determine the behavior of the :defined pseudo-class. Whether or not an element is custom is used to determine the behavior of the mutation algorithms.

The following code illustrates elements in each of these three states:

<!DOCTYPE html>
<script>
  window.customElements.define("sw-rey", class extends HTMLElement {})
  window.customElements.define("sw-finn", class extends HTMLElement {}, { extends: "p" })
  window.customElements.define("sw-kylo", class extends HTMLElement {
    constructor() {
      super()
      throw new Error("The droid... stole a freighter?")
    }
  })
</script>

<!-- "undefined" (not defined, not custom) -->
<sw-han></sw-han>
<sw-kylo></sw-kylo>
<p is="sw-luke"></p>
<p is="asdf"></p>

<!-- "uncustomized" (defined, not custom) -->
<p></p>
<asdf></asdf>

<!-- "custom" (defined, custom) -->
<sw-rey></sw-rey>
<p is="sw-finn"></p>

Additionally, after the talk about qualified names but before the discussion of attributes, the following algorithm should be inserted:

To create an element, given a document, prefix, localName, namespace, is, and optional synchronous custom elements flag, run the following steps:

  1. Let result be null.
  2. Let definition be the result of looking up a custom element definition given document, namespace, localName, and is.
  3. If definition is non-null, and definition's name is not equal to its local name (i.e. definition represents a type extension), then:

    1. Let interface be the element interface for localName and the HTML namespace.
    2. Set result to a new element that implements interface, with no attributes, namespace set to the HTML namespace, namespace prefix set to prefix, local name set to localName, custom element state "undefined", and node document set to document.
    3. If the synchronous custom elements flag is set, upgrade element using definition.
    4. Otherwise, enqueue a custom element upgrade reaction given result and definition.
  4. Otherwise, if definition is non-null, then:

    1. If synchronous custom elements flag is set:

      1. Let C be definition's constructor.
      2. Set result to Construct(C). Rethrow any exceptions.
      3. If result does not implement the HTMLElement interface, throw a TypeError exception.

        This is meant to be a brand check to ensure that the object was allocated by the HTMLElement constructor. Eventually Web IDL may give us a more precise way to do brand checks.

      4. If the value of result's [[\Prototype]] internal slot is not equal to definition's prototype, throw a TypeError exception.
      5. If result's attribute list is not empty, throw a NotSupportedError exception.
      6. If result has children, throw a NotSupportedError exception.
      7. If result's parent is not null, throw a NotSupportedError exception.
      8. If result's node document is not document, throw a NotSupportedError exception.
      9. If result's namespace is not the HTML namespace, throw a NotSupportedError exception.
      10. If result's local name is not localName, throw a NotSupportedError exception.
      11. Set result's namespace prefix to prefix.
      12. Set result's custom element state to "custom".
    2. Otherwise:

      1. Set result to a new element that implements the HTMLElement interface, with no attributes, namespace set to the HTML namespace, namespace prefix set to prefix, local name set to localName, custom element state "undefined", and node document set document.
      2. Enqueue a custom element upgrade reaction given result and definition.
  5. Otherwise:

    1. Let interface be the element interface for localName and namespace.
    2. Set result to a new element that implements interface, with no attributes, namespace set to namespace, namespace prefix set to prefix, local name set to localName, custom element state "uncustomized", and node document set to document.
    3. If document has a browsing context, namespace is the HTML namespace, and either localName is a valid custom element name or is is non-null, set result's custom element state to "undefined".
  6. Return result.

DOM+: Cloning

The clone a node algorithm will need to be modified as follows. A new case, separate from the main switch, will be needed to generate copy for Elements. It should use create an element, given document, node's local name, node's namespace prefix, node's namespace, and the value of node's is attribute (if present). The synchronous custom elements flag should be unset.

DOM+: Mutation algorithms

The mutation algorithms sections need to be modified as follows to properly enqueue custom element callbacks.

Modify the insert algorithm as follows. Replace step 6.2 with:

  1. For each shadow-including inclusive descendant inclusiveDescendant of node, in shadow-including tree order, run these subsubsteps:

    1. Run the insertion steps with inclusiveDescendant and parent.
    2. If inclusiveDescendant is in a shadow-including document, then:

      1. If inclusiveDescendant is custom, enqueue a custom element callback reaction with inclusiveDescendant, callback name "connectedCallback", and an empty argument list.
      2. Otherwise, try to upgrade inclusiveDescendant.

        If this successfully upgrades inclusiveDescendant, its connectedCallback will be enqueued automatically during the upgrade an element algorithm.

Modify the remove algorithm as follows. Replace step 9 with:

  1. For each shadow-including inclusive descendant inclusiveDescendant of node, in shadow-including tree order, run these subsubsteps:

    1. Run the removing steps with inclusiveDescendant and parent.
    2. If inclusiveDescendant is custom and is not in a shadow-including document, enqueue a custom element callback reaction with inclusiveDescendant, callback name "disconnectedCallback", and an empty argument list.

Modify the change an attribute algorithm as follows. Add an additional step after step 1:

  1. If element is custom, enqueue a custom element callback reaction with element, callback name "attributeChangedCallback", and an argument list containing attribute's local name, attribute's value, value, and attribute's namespace.

Modify the append an attribute algorithm as follows. Add an additional step after step 1:

  1. If element is custom, enqueue a custom element callback reaction with element, callback name "attributeChangedCallback", and an argument list containing attribute's local name, null, attribute's value, and attribute's namespace.

Modify the remove an attribute algorithm as follows. Add an additional step after step 1:

  1. If element is custom, enqueue a custom element callback reaction with element, callback name "attributeChangedCallback", and an argument list containing attribute's local name, attribute's value, null, and attribute's namespace.

Modify the replace an attribute algorithm as follows. Add an additional step after step 1:

  1. If element is custom, enqueue a custom element callback reaction with element, callback name "attributeChangedCallback", and an argument list containing oldAttr's local name, oldAttr's value, newAttr's value, and oldAttr's namespace.

DOM+: Document methods

To allow creating both custom tag and type extension-style custom elements, the createElement or createElementNS methods gain optional typeExtension arguments. Their new definitions are:

partial interface Document {
    Element createElement(DOMString localName, optional ElementCreationOptions options);
    Element createElementNS(DOMString? namespace, DOMString qualifiedName, optional ElementCreationOptions options);
};

dictionary ElementCreationOptions {
    DOMString is;
};
The createElement(localName, options) method, when invoked, must run these steps:
  1. If localName does not match the Name production, throw an InvalidCharacterError exception.
  2. If the context object is an HTML document, let localName be converted to ASCII lowercase.
  3. Let is be the value of the is member of options, or null if no such member exists.
  4. Let definition be the result of looking up a custom element definition, given the context object, the HTML namespace, localName, and is.
  5. If is is non-null, but definition is null, throw a NotFoundError.
  6. Let element be the result of creating an element given the context object, localName, null, the HTML namespace, is, and with the synchronous custom elements flag set. Rethrow any exceptions.
  7. If is is non-null, set an attribute value for element using "is" and is.
  8. Return element.
The createElementNS(namespace, qualifiedName, options) method, when invoked, must run these steps:
  1. Let namespace, prefix, and localName be the result of passing namespace and qualifiedName to validate and extract. Rethrow any exceptions.
  2. Let is be the value of the is member of options, or null if no such member exists.
  3. Let definition be the result of looking up a custom element definition, given the context object, namespace, localName, and is.
  4. If is is non-null, but definition is null, throw a NotFoundError.
  5. Let element be the result of creating an element given the context object, localName, prefix, namespace, typeExtension, and with the synchronous custom elements flag set. Rethrow any exceptions.
  6. If is is non-null, set an attribute value for element using "is" and is.
  7. Return element.

Acknowledgments

David Hyatt developed XBL 1.0, and Ian Hickson co-wrote XBL 2.0. These documents provided tremendous insight into the problem of behavior attachment and greatly influenced this specification.

Alex Russell and his considerable forethought triggered a new wave of enthusiasm around the subject of behavior attachment and how it can be applied practically on the Web.

Dominic Cooney, Hajime Morrita, and Roland Steiner worked tirelessly to scope the problem within the confines of the Web platform and provided a solid foundation for this document.

Steve Faulkner, The Paciello Group, for writing the content of the section .

The <flag-icon> example was inspired by a custom element by Steven Skelton. (MIT)

The editor would also like to thank Alex Komoroske, Andres Rios, Anne van Kesteren, Boris Zbarsky, Daniel Buchner, Edward O'Connor, Erik Arvidsson, Elliott Sprehn, Hayato Ito, Jan Miksovsky, Jonas Sicking, Olli Pettay, Rafael Weinstein, Ryosuke Niwa, Scott Miles, Simon Pieters, Steve Orvell, Tab Atkins, Tim Perry, and William Chen for their comments and contributions to this specification.

This list is too short. There's a lot of work left to do. Please contribute by reviewing and filing bugs—and don't forget to ask the editor to add your name into this section.