Custom Elements

W3C Editor's Draft

This version
http://w3c.github.io/webcomponents/spec/custom/
Latest published version
http://www.w3.org/TR/custom-elements/
Latest editor's draft
http://w3c.github.io/webcomponents/spec/custom/
Previous version
http://www.w3.org/TR/custom-elements/
Revision history
https://github.com/w3c/webcomponents/commits/gh-pages/spec/custom/
Participate
Discuss on public-webapps@w3.org (Web Applications Working Group)
File bugs (w3.org's Bugzilla)
Editor
Dimitri Glazkov, Google, <>

Abstract

This specification describes the method for enabling the author to define and use new types of DOM elements in a document.

Status of This Document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at http://www.w3.org/TR/.

This document was published by the Web Applications Working Group as an Editor's Draft. If you wish to make comments regarding this document, please send them to public-webapps@w3.org (subscribe, archives). All feedback is welcome.

Publication as an Editor's Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

Table of Contents

  1. About this Document
  2. Dependencies
  3. Motivations
  4. Concepts
  5. Custom Element Lifecycle
    1. Enqueueing and Invoking Callbacks
    2. Types of Callbacks
  6. Creating and Passing Registries
  7. Registering Custom Elements
    1. Extensions to Document Interface
    2. Unresolved Element Pseudoclass
  8. Instantiating Custom Elements
    1. Extensions to Document Interface
  9. Parsing Custom Elements
  10. Custom Elements and ECMAScript 6
  11. Custom Element Semantics
    1. Custom Tag Example
    2. Type Extension Example
    3. Custom Element Semantics - Conclusion
  12. Appendix A: All Algorithms in One Diagram
  13. Acknowledgements

About this Document

All diagrams, examples, notes, are non-normative, as well as sections explicitly marked as non-normative. Everything else in this specification is normative.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the normative parts of this document are to be interpreted as described in RFC2119. For readability, these words do not appear in all uppercase letters in this specification.

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.

Dependencies

This document relies on the following specifications:

Motivations

There are two motivations that fueled the development of this specification:

  1. Provide a way for Web developers to build their own, fully-featured DOM elements. Though it was long possible to create DOM elements with any tag names in HTML, these elements weren't very functional. By giving Web developers the means to both inform the parser on how to properly construct an element and to react to lifecycle changes of an element, the specification eliminates the need for DOM-as-a-render-view scaffolding that has to exist today in most web frameworks or libraries.
  2. Rationalize the platform. The specification ensures that all of its new features and abilities are in concert with how the relevant bits of the Web platform work today, so that these new features could be used to explain the functionality of existing Web platform features, such as HTML elements.

Most of the effort went into finding the right balance between the two motivations, driven by the hope that these motivations do not run counter to each other, but are rather complementary parts of the same larger story. For example, though the scope of the spec is currently limited to only creating custom elements by authors, it is designed to shorten the distance to a much more ambitious goal of rationalizing all HTML, SVG, and MathML elements into one coherent system.

Concepts

Custom element is platform object whose interface is defined by the author. The interface prototype object of a custom element's interface is called the custom element prototype.

The custom element type identifies a custom element interface and is a sequence of characters that must match the NCName production, must contain a U+002D HYPHEN-MINUS character, and must not contain any uppercase ASCII letters. The custom element type must not be one of the following values:

The list of names above is the summary of all hyphen-containing element names from the applicable specifications, namely SVG and MathML.

The element definition describes a custom element and consists of:

At the time of creation, a document could be associated with a registry. A registry is a set of element definitions.

Element registration is a process of adding an element definition to a registry. One element definition can only be registered with one registry.

If a document has a registry associated with it, then for this document and a given element definition in the registry, the custom element's interface must be the element interface for local name and namespace values of custom element type and the namespace of the element definition, respectively.

Effectively, the registry is consulted whenever a new DOM element is created, whether imperatively or by a parser. Whenever a matching element definition is found in the registry, the information in this definition is used to create a new instance of a custom element.

If a document does not have a registry associated with it, all attempts at element registration will fail.

The exact nature of creating registries, their association with documents, and element registration are defined further in this specification.

Custom Element Lifecycle

A custom element can go through these changes during its lifetime:

Various callbacks can be invoked when a custom element goes through some of these changes. These callbacks are stored internally as a collection of key-value pairs and called lifecycle callbacks.

To transfer a callback named name from an object property named property to lifecycle callbacks, the user agent must run the following steps:

Input
NAME, name of the callback
PROPERTY, name of the property
OBJECT, the object from which PROPERTY is being transferred
LIFECYCLE, the lifecycle callbacks
Output
None
  1. Let CALLBACK be the result of getting a property named PROPERTY of OBJECT
  2. If CALLBACK exists and is a callable object, add CALLBACK to LIFECYCLE, associated with the key NAME.

Enqueuing and Invoking Callbacks

To facilitate invoking callbacks, each unit of related similar-origin browsing contexts has a processing stack, which is initially empty. Each item in the stack is an element queue, which is initially empty as well. Each item in the element queue is a custom element.

Each custom element has an associated callback queue and an element is being created flag. The flag is initially set to false and the callback queue is initially empty. Each item in the queue consists of the callback itself and zero or more string values that are used as callback arguments.

To invoke callbacks in an element queue, the user agent must run these steps or their equivalent:

Input
QUEUE, an element queue
Output
None
  1. For each custom element ELEMENT in QUEUE:
    1. Let CALLBACKS be the ELEMENT's callback queue
    2. Repeat until CALLBACKS is empty:
      1. Remove the first item from CALLBACKS and let CALLBACK be this item
      2. Invoke CALLBACK with ELEMENT as callback this value and, if present, using string values in CALLBACK as arguments

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 lifecycle callback, the user agent could choose not to wrap it as a performance optimization.

In addition to an element queue, there is also a sorted element queue. The custom elements are kept in the order of increasing custom element order.

The custom element order is a sum of document custom element order and import tree order, in which the import tree order is scaled so that its lowest value is always larger than the highest possible value of document custom element order.

The document custom element order is a numerical value, associated with every custom element. This value is as a result of custom element's document keeping a numerical value that is incremented and assigned to custom element as its custom element order whenever the following occurs:

The import tree order of a given custom element of an import link tree is determined by tree order in an import link tree that was flattened by replacing every import link with the content of its imported document.

The highest stable order is the value that is immediately preceeding the custom element order of an element in the first encountered import, in tree order, that has not yet completely loaded. If there is no such element, the highest stable order is the highest custom element order in the flattened import link tree.

Because imports load asynchronously, we need to divide a sorted element queue into the part where things have settled down (all imports have loaded), and the part where the loading is still happening, and thus the actual sorting order is not yet determined. For example, suppose you have the following document structure:


index.html:
<link rel="import" href="import.html">
...
<me-second></me-second>
...

import.html:
<me-first></me-first>

The order of custom elements in the flattened import link tree is me-first (1), me-second (2). However, it's very likely that the parser will find out about me-second sooner than me-first, since the latter requires loading the import.html. While the network stack is doing its job, the highest stable order stays at the beginning position. When import.html is ready, the order jumps all the way to me-second (2).

Each unit of related similar-origin browsing contexts has an initially-empty sorted element queue, called base element queue.

Whenever a base element queue becomes non-empty, the user agent must queue a microtask to process base element queue for the unit of related similar-origin browsing contexts to which the scripts' browsing context belongs.

To prevent reentrance while processing base element queue, each unit of related similar-origin contexts has a processing base element queue flag, which must initially be false.

To process base element queue, a conforming user agent must run the following steps or their equivalent:

Input
ENVIRONMENT, the unit of related similar-origin browsing contexts
Output
None
  1. Let PROCESSING be the processing base element queue flag
  2. If PROCESSING is true, stop.
  3. Set PROCESSING to true.
  4. Invoke callbacks in ENVIRONMENT's base element queue up to the highest stable order, inclusively
  5. Set PROCESSING to false.

In the unit of related similar-origin browsing contexts to which the scripts' browsing context belongs, the current element queue is the element queue at the top of the processing stack or the base element queue if the processing stack is empty.

To enqueue a lifecycle callback, the user must run the following steps or their equivalent:

Input
NAME, name of the callback
ELEMENT, the custom element for which the callback is enqueued
Output
None
  1. Let DEFINITION be ELEMENT's definition
  2. If DEFINITION does not exist, let CALLBACK be null and stop.
  3. Let CALLBACKS be the lifecycle callbacks from DEFINITION
  4. Let CALLBACK be the callback, associated with the key NAME in CALLBACKS
  5. If there is no such callback, stop.
  6. Add CALLBACK to ELEMENT's callback queue
  7. If element is being created flag is false, add ELEMENT to current element queue.

Types of Callbacks

The following callbacks are recognized:

createdCallback
This callback is invoked after custom element instance is created and its definition is registered. The actual timing of this callback is defined further in this specification.
The custom element prototype must be set just prior to invoking callback.
For the duration of this callback invocation, the element is being created flag must be set to true. In all other cases, the flag must be set to false.
If the created callback exists for an element, all other callbacks must not be enqueued until after this created callback's invocation had started.
attachedCallback
Unless specified otherwise, this callback must be enqueued whenever custom element is inserted into a document and this document has a browsing context.
detachedCallback
Unless specified otherwise, this callback must be enqueued whenever custom element is removed from the document and this document has a browsing context.
attributeChangedCallback
Unless specified otherwise, this callback must be enqueued whenever custom element's attribute is added, changed or removed. Depending on the type of attribute modification, the following additional strings are added to the queue item:
attribute is set
attribute local name, null, new attribute value, and the attribute namespace.
attribute is changed
attribute local name, old attribute value, new attribute value, and the attribute namespace.
attribute is removed
attribute local name, old attribute value, null, and the attribute namespace.

To set custom element prototype on a custom element, a conforming user agent must run the following steps or their equivalent:

Input
ELEMENT, element
Output
None
  1. Let PROTOTYPE be the custom element prototype in ELEMENT's definition
  2. Set the value of the [[Prototype]] internal property of ELEMENT to PROTOTYPE.
  3. If ELEMENT is in a document and this document has a browsing context:
    1. Enqueue attached callback for ELEMENT

Creating and Passing Registries

When an HTML Document is loaded in a browsing context, a new registry must be created and associated with this document.

A new document instance must be associated with an existing registry in these cases:

In all other cases, new documents must not have a registry.

Registering Custom Elements

Because element registration can occur at any time, a custom element could be created before its definition is registered. Such custom element instances are called unresolved elements. When an unresolved element is created, and if its element interface was not defined by HTML or other applicable specifications, the unresolved element's element interface must be:

The effect of this statement is that any HTML (or SVG) element with the local name that is a valid custom element type will have HTMLElement (or SVGElement) as element interface, rather than HTMLUnknownElement.

Each registry has an associated map of all instances of unresolved elements for a given pair of custom element type and namespace. This data structure is called the upgrade candidates map and is initially empty. Each value item in this map is a sorted element queue.

Whenever an unresolved element is created, it must be added to the respective sorted element queue in upgrade candidates map.

Registering an element definition is the responsibility of the element registration algorithm, which must be equivalent to running these steps:

Input
DOCUMENT, the document
TYPE, the custom element type of the element being registered
PROTOTYPE, the custom element prototype
NAME, a local name, optional
Output
ERROR, a variable that holds one of these values: None, InvalidType, InvalidName, NoRegistry, or DuplicateDefinition
  1. Let ERROR and DEFINITION be the result of running definition construction algorithm with DOCUMENT, TYPE, PROTOTYPE, and NAME as arguments
  2. If ERROR is not None, stop.
  3. Let REGISTRY be DOCUMENT's registry
  4. If REGISTRY does not exist, set ERROR to NoRegistry and stop.
  5. Add DEFINITION to REGISTRY
  6. Let MAP be REGISTRY's upgrade candidates map
  7. Run element upgrade algorithm with MAP and DEFINITION as arguments.

The definition construction algorithm creates an element definition and must be equivalent to running these steps:

Input
DOCUMENT, the document
TYPE, the custom element type of the element being registered
PROTOTYPE, the custom element prototype
NAME, a local name, optional
Output
DEFINITION, the element definition
ERROR, a variable that holds one of these values: None, InvalidType, InvalidName, or DuplicateDefinition
  1. Let ERROR be None
  2. Convert TYPE to ASCII lowercase
  3. If DOCUMENT is an HTML document, convert NAME to ASCII lowercase
  4. If TYPE is an invalid custom element type, set ERROR to InvalidType and stop.
  5. Let NAMESPACE be HTML Namespace
  6. Let IS-SVG be the result of running SVGElement inheritance detection algorithm with PROTOTYPE and the global environment of DOCUMENT as arguments
  7. If IS-SVG is true, set NAMESPACE to SVG Namespace
  8. If there already exists a definition with the same TYPE, set ERROR to DuplicateDefinition and stop.
  9. If NAME was provided and is not null:
    1. Let BASE be the element interface for NAME and NAMESPACE
    2. If BASE does not exist or is an interface for a custom element, set ERROR to InvalidName and stop.
  10. Otherwise:
    1. If NAMESPACE is SVG Namespace, set ERROR to InvalidName and stop.
    2. Let NAME be TYPE
  11. Let LIFECYCLE be lifecycle callbacks
  12. Transfer callback named createdCallback to LIFECYCLE from property named createdCallback on PROTOTYPE
  13. Transfer callback named attachedCallback to LIFECYCLE from property named attachedCallback on PROTOTYPE
  14. Transfer callback named detachedCallback to LIFECYCLE from property named detachedCallback on PROTOTYPE
  15. Transfer callback named attributeChangedCallback to LIFECYCLE from property named attributeChangedCallback on PROTOTYPE
  16. Let DEFINITION be an element definition with custom element type set to TYPE, local name to NAME, namespace to NAMESPACE, custom element prototype to PROTOTYPE, and lifecycle callbacks to LIFECYCLE.

The SVGElement inheritance detection algorithm must run these steps:

Input
DESCENDANT, an object whose inheritance is being detected
ENVIRONMENT, a global environment
Output
RESULT, true, if DESCENDANT inherits from SVGElement or false otherwise
  1. Set RESULT to true
  2. Let SVG-PROTOTYPE be the SVGElement's interface prototype object for ENVIRONMENT
  3. Let PROTOTYPE be DESCENDANT
  4. Repeat until PROTOTYPE is undefined:
    1. If PROTOTYPE strictly equals to SVG-PROTOTYPE, stop and return RESULT.
    2. Let PROTOTYPE be the result of getting a prototype of PROTOTYPE
  5. Set RESULT to false
  6. Return RESULT.

The element upgrade algorithm upgrades unresolved elements whose definition is now registered and must be equivalent to running these steps:

Input
Let MAP, an upgrade candidates map
DEFINITION, element definition
Output
None
  1. Let TYPE be the custom element type in DEFINITION
  2. Let CANDIDATES be the sorted element queue for TYPE and NAMESPACE in MAP
  3. For each item ELEMENT in CANDIDATES:
    1. Enqueue created callback for ELEMENT
  4. Set CANDIDATES to empty sorted element queue.

Extensions to Document Interface

The registerElement method of the Document interface provides a way to register a custom element and returns its custom element constructor.


partial interface Document {
    Function registerElement(DOMString type, optional ElementRegistrationOptions options);
};

dictionary ElementRegistrationOptions {
     object? prototype = null;
     DOMString? extends = null;
};

When called, the registerElement method must run these steps:

Input
DOCUMENT, method's context object, a document
TYPE, the custom element type of the element being registered
PROTOTYPE, the custom element prototype, optional
EXTENDS, the local name of an HTML or SVG element that is being extended, optional
Output
CONSTRUCTOR, the custom element constructor
  1. If PROTOTYPE is null, let PROTOTYPE be the result of invoking Object.create with HTMLElement's interface prototype object as only argument
  2. Let NAME be EXTENDS
  3. Let ERROR be the result of running the element registration algorithm with DOCUMENT, TYPE, PROTOTYPE, and NAME as arguments
  4. If ERROR is InvalidType, throw a SyntaxError and stop.
  5. If ERROR is not None, throw a NotSupportedError and stop.
  6. Return result of running custom element constructor generation algorithm with DOCUMENT and PROTOTYPE as arguments.

ElementRegistrationOptions is an abstraction that enables using function objects and ES6 classes as the second argument of document.register method.

In order to register a custom element with a prototype, other than HTMLElement or SVGElement, the caller of document.registerElement has to first build a proper prototype object that inherits from HTMLElement. Here's a simple example of how one could do this:


document.registerElement('x-foo', {
    prototype: Object.create(HTMLParagraphElement.prototype, {
        firstMember: {
            get: function() { return "foo"; },
            enumerable: true,
            configurable: true
        },
        // specify more members for your prototype.
        // ...
    }),
    extends: 'p'
});

Note the use of extends option to specify that the element is being registered as a type extension -- that is, this element does not introduce a new tag (like the custom tag elements do), but rather extends an existing element of type HTMLParagraphElement. Here's how one could instantiate this element:


<p is="x-foo">Paragraph of amazement</p>
Or imperatively, in JavaScript:

var foo = document.createElement('p', 'x-foo');

Elements with SVGElement prototype deserve a special mention: using custom tag approach results in ignored elements in SVG. Thus, your SVG-based custom elements would almost always be type extensions.

Unresolved Element Pseudoclass

The :unresolved pseudoclass must match all custom elements whose created callback has not yet been invoked.

The :unresolved pseudoclass could be used to mitigate the Flash of Unstyled Content (FOUC) issues with custom elements.

Instantiating Custom Elements

The custom element type is given to a custom element at the time of its instantation in one of the two ways:

  1. As the local name of the custom element. These types of custom element types are called custom tags.
  2. As the value of the is attribute of the custom element. custom element types given this way are called type extensions.

After a custom element is instantiated, changing the value of the is attribute must not affect this element's custom element type.

If both types of custom element types are provided at the time of element's instantiation, the custom tag must win over the type extension.

All custom elements must be constructable with a function object, called custom element constructor. This constructor must be created with the custom element constructor generation algorithm, which must be equivalent to running these steps:

Input
PROTOTYPE, the custom element prototype.
DOCUMENT, the owner document for new custom element
Output
CONSTRUCTOR, the custom element constructor
  1. If PROTOTYPE is already an interface prototype object for any interface object or PROTOTYPE has a non-configurable property named constructor, throw a NotSupportedError and stop.
  2. Let DEFINITION be an element definition that has PROTOTYPE as custom element prototype
  3. Let CONSTRUCTOR be the interface object whose interface prototype object is PROTOTYPE and when called as a constructor, executes these steps:
    1. Let ELEMENT be the context object
    2. Let TYPE be the custom element type in DEFINITION
    3. Let NAME be the local name in DEFINITION
    4. Let NAMESPACE be the namespace in DEFINITION
    5. Set ELEMENT's local name to NAME, namespace to the NAMESPACE, and node document to DOCUMENT
    6. If TYPE is not the same as NAME, set the value of ELEMENT's is attribute to TYPE
    7. Enqueue created callback for ELEMENT
    8. Return ELEMENT.

Extensions to Document Interface

To allow creating both custom tag and type extension-style custom elements, the createElement or createElementNS methods have overloads with a typeExtension argument:


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

Instead of step 3 in createElement and step 9 in createElementNS (the steps that determine element interface, both methods must run the following steps:

  1. Let TYPE be typeExtension, or localName if typeExtension is not present
  2. If an element definition with matching localName, namespace, and TYPE is not registered with token's document, set TYPE to localName
  3. Let interface be the element interface for TYPE as local name and namespace (HTML Namespace for createElement)

Additionally, both createElement or createElementNS methods must run the following steps just before returning the result:

  1. If TYPE is not the same as localName, set the value of ELEMENT's is attribute to TYPE
  2. Enqueue created callback for ELEMENT

Parsing Custom Elements

To enable instantiating custom elements during tree construction, a conforming UA must run enqueue created callback whenever creating a custom element.

This modification to tree construction has the consequence of custom elements being created when parsing HTML documents or fragments.

Custom Elements and ECMAScript 6

Once the ECMAScript Standard Edition 6 is released, this section will be integrated into the respective areas of this specification. Until then, here is an overview of how ECMAScript 6 and Custom Elements integrate.

If the user agent implements the @@create method, this specification would stop treating the ElementRegistrationOptions options argument in registerElement as a dictionary, and instead view it as a the custom element constructor.

Instead of generating a constructor, the user agent will now mutate this argument to have a new @@create method that creates a new element object.

Since the registerElement's second argument is now a constructor function, the element definition should change to hold that constructor function, rather than the custom element prototype.

To accommodate this change, the element registration algorithm to the following steps:

Input
DOCUMENT, the document
TYPE, the custom element type of the element being registered
FUNCTION, the custom element constructor
NAME, a local name, optional
Output
ERROR, a variable that holds one of these values: None, InvalidType, InvalidName, NoRegistry, or DuplicateDefinition
  1. Let ERROR and DEFINITION be the result of running definition construction algorithm with DOCUMENT, TYPE, PROTOTYPE, and NAME as arguments
  2. If ERROR is not None, stop.
  3. Let REGISTRY be DOCUMENT's registry
  4. If REGISTRY does not exist, set ERROR to NoRegistry and stop.
  5. Add DEFINITION to REGISTRY
  6. Let MAP be REGISTRY's upgrade candidates map
  7. Run element upgrade algorithm with MAP and DEFINITION as arguments.

The steps run when calling registerElement will change to:

Input
DOCUMENT, method's context object, a document
TYPE, the custom element type of the element being registered
FUNCTION, the custom element constructor, optional
EXTENDS, the local name of an HTML or SVG element that is being extended, optional
Output
CONSTRUCTOR, the custom element constructor
  1. If FUNCTION is null:
    1. Let FUNCTION be the result of calling FunctionAllocate with HTMLElement as the functionPrototype and true as strict
    2. Let PROTOTYPE be the result of calling ObjectCreate with HTMLElement's interface prototype object as only argument
    3. Call DefinePropertyOrThrow(PROTOTYPE, "constructor", PropertyDescriptor{[[Value]]: FUNCTION, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false})
    4. Call DefinePropertyOrThrow(FUNCTION, "prototype", PropertyDescriptor{[[Value]]: PROTOTYPE, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false})
  2. Otherwise:
    1. Let PROTOTYPE be the result of Get(FUNCTION, "prototype")
  3. Let NAME be EXTENDS
  4. Let ERROR be the result of running the element registration algorithm with DOCUMENT, TYPE, PROTOTYPE, and NAME as arguments
  5. If ERROR is InvalidType, throw a SyntaxError and stop.
  6. If ERROR is not None, throw a NotSupportedError and stop.
  7. Return result of running custom element constructor generation algorithm with PROTOTYPE, FUNCTION, and DOCUMENT as arguments.

Similarly, the custom element constructor generation algorithm will change as follows:

Input
PROTOTYPE, the custom element prototype
FUNCTION, the custom element constructor
DOCUMENT, the owner document for new custom element
Output
FUNCTION, the mutated custom element constructor
  1. If FUNCTION is already an interface object for any interface, throw a NotSupportedError and stop.
  2. Let DEFINITION be an element definition that has PROTOTYPE as custom element prototype
  3. Let CREATE be a function which when called, executes these steps:
    1. Let ELEMENT be the context object
    2. Let TYPE be the custom element type in DEFINITION
    3. Let NAME be the local name in DEFINITION
    4. Let NAMESPACE be the namespace in DEFINITION
    5. Set ELEMENT's local name to NAME, namespace to the NAMESPACE, and node document to DOCUMENT
    6. If TYPE is not the same as NAME, set the value of ELEMENT's is attribute to TYPE
    7. Enqueue created callback for ELEMENT
    8. Return ELEMENT.
  4. Call DefinePropertyOrThrow(FUNCTION, @@create, PropertyDescriptor{[[Value]]: CREATE, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false})
  5. Return FUNCTION.

Custom Element Semantics

The default semantics of a custom element is dependent upon the form in which it is instantiated:

Custom Tag Example

For example, a custom tag could be named taco-button, but the name alone does not express the semantics of a HTML button element simply due to its name. As instantiated a custom tag conveys a similar amount of semantics as an HTML div or span element:

<!-- taco-button represents a span with a fancy name -->
<taco-button></taco-button>

The addition of visual styling and scripted events to the taco-button could provide hints as to its semantics and expected interaction behaviours — for some users — but for the semantics to be formally expressed developers must convey the semantics using ARIA roles, states and properties.

The addition of a tabindex attribute to the custom element provides interaction (the element is included in the focus order) and property/state semantics (it exposes information that it is focusable and if it currently has focus).


<!-- taco-button represents a focusable span with a fancy name -->
<taco-button tabindex="0">Eat Me</taco-button>

The addition of a label, using aria-label, to the custom element provides an Accessible Name for the element.


<!-- taco-button represents a focusable span with a fancy name and a text label -->
<taco-button tabindex="0" aria-label="Eat Me">Eat Me</taco-button>

The addition of keyboard event handlers to the custom element element provides the means for keyboard users to operate the control, but does not convey the presence of the functionality.


<!-- taco-button represents focusable span with a fancy name, a text label and button like event handling -->
<taco-button tabindex="0" onclick="alert('tasty eh?');"
onkeypress="if(event.keyCode==32||event.keyCode==13){alert('tasty eh?');};">Eat Me</taco-button>

The addition of inline event handlers are for demonstration purposes only. The event handlers could be added by the lifecycle callbacks imperatively, or maybe even not used at all. This example demonstrates one method for developers to ensure that a custom control is operable for keyboard users and meets the WCAG 2.0 criteria "All functionality of the content is operable through a keyboard interface".

The addition of an ARIA role="button" conveys the custom element's role semantics, which enables users to successfully interact with the control using the expected button interaction behaviours (pressing the space or enter keys to activate).


<!-- taco-button represents a focusable button with a text label and button like event handling -->
<taco-button role="button" tabindex="0" onclick="alert('tasty eh?');"
onkeypress="if(event.keyCode==32||event.keyCode==13){alert('tasty eh?');};">Eat Me</taco-button>

The developer may provide a disabled state for the custom element. This could be implemented by removing the tabindex attribute so the element is no longer included in the focus order and removing the functionality so that interacting with the element does nothing. Also the visual styling may also be modified to visually indicate it the element is disabled.


<!-- grayed out non focusable taco-button with functionality removed, to indicate the button is in a disabled state  -->
<taco-button role="button" tabindex="0" onclick="alert('tasty eh?');" 
onkeypress="if(event.keyCode==32||event.keyCode==13){alert('tasty eh?');};">Eat Me</taco-button>

Removing the focusability and functionality of the custom element and modifying its style does not unambiguously express that it is in a disabled state. To unambiguously express the disabled state add aria-disabled="true".

A disabled attribute would not work here as the custom tag is not based on an HTML element that suports its use.


<!-- taco-button represents a focusable button with a text label and button like event handling -->
<taco-button role="button" tabindex="0" onclick="alert('tasty eh?');" 
onkeypress="if(event.keyCode==32||event.keyCode==13){alert('tasty eh?');};" aria-disabled="true">Eat Me</taco-button>

Type Extension Example

A type extension, for example could extend the HTML button element. As instantiated it would inherit the button element's name, role, states and properties, built in focus and keyboard interaction behaviours.

<!-- tequila-button represents a button with an accessible name of "Drink Me!" -->
<button is="tequila-button">Drink Me!</button>

To implement the desired tequila-button feature, all that is required is the addition of an event handler. The rest of the semantics and interaction behaviour are provided by the browser as part of its implementation of the button element.

<!-- tequila-button represents a button -->
<button is="tequila-button" onclick="alert('smooth!');">Drink Me!</button>

To implement the disabled state on the tequila-button, all that is required is the addition of the HTML disabled attribute. The semantics, style and interaction behaviour are implemented by the browser.

<!-- tequila-button represents a button -->
<button is="tequila-button" onclick="alert('smooth!');" disabled>Drink Me!</button>

Custom Element Semantics — Conclusion

The simplest and most robust method to create custom elements that are usable and accessible is to implement custom elements as type extensions. This method provides a custom element with built in semantics and interaction behaviours that developers can use as a foundation.

Use ARIA, where needed, to provide semantics for custom elements and follow the ARIA Design Patterns when implementing ARIA attributes and UI interaction behaviours. Ensure that custom tag or type extension custom elements meet the criteria listed in the Custom Control Accessible Development Checklist. Use ARIA in accordance with the Document conformance requirements for use of ARIA attributes in HTML.

Further Reading for Developers

Appendix A: All Algorithms in One Diagram

To help navigate through various parts of the spec and their interactions, here is a diagram that attempts to put all algorithms actions that trigger them in one space. Click on each box to go to the corresponding algorithm or action.

Fig. All algorithms in one diagram

Acknowledgements

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 for the Custom Element Semantics section

The editor would also like to thank Alex Komoroske, Anne van Kesteren, Boris Zbarsky, Daniel Buchner, Edward O'Connor, Erik Arvidsson, Elliott Sprehn, Hayato Ito, Jonas Sicking, Olli Pettay, Rafael Weinstein, Scott Miles, Simon Pieters, Steve Orvell, Tab Atkins, 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.