Working with Custom Elements
Introduction #
The web severely lacks expression. To see what I mean, take a peek at a "modern" web app like GMail:
There's nothing modern about <div>
soup. And yet, this is how we build web apps. It's sad. Shouldn't we demand more from our platform?
Sexy markup. Let's make it a thing #
HTML gives us an excellent tool for structuring a document but its vocabulary is limited to elements the HTML standard defines.
What if the markup for GMail wasn't atrocious? What if it was beautiful:
<hangout-module>
<hangout-chat from="Paul, Addy">
<hangout-discussion>
<hangout-message from="Paul" profile="profile.png"
profile="118075919496626375791" datetime="2013-07-17T12:02">
<p>Feelin' this Web Components thing.
<p>Heard of it?
</hangout-message>
</hangout-discussion>
</hangout-chat>
<hangout-chat>...</hangout-chat>
</hangout-module>
How refreshing! This app totally makes sense too. It's meaningful, easy to understand, and best of all, it's maintainable. Future me/you will know exactly what it does just by examining its declarative backbone.
Getting started #
Custom Elements allow web developers to define new types of HTML elements. The spec is one of several new API primitives landing under the Web Components umbrella, but it's quite possibly the most important. Web Components don't exist without the features unlocked by custom elements:
- Define new HTML/DOM elements
- Create elements that extend from other elements
- Logically bundle together custom functionality into a single tag
- Extend the API of existing DOM elements
Registering new elements #
Custom elements are created using document.registerElement()
:
var XFoo = document.registerElement('x-foo');
document.body.appendChild(new XFoo());
The first argument to document.registerElement()
is the element's tag name. The name must contain a dash (-). So for example, <x-tags>
, <my-element>
, and <my-awesome-app>
are all valid names, while <tabs>
and <foo_bar>
are not. This restriction allows the parser to distinguish custom elements from regular elements but also ensures forward compatibility when new tags are added to HTML.
The second argument is an (optional) object describing the element's prototype
. This is the place to add custom functionality (e.g. public properties and methods) to your elements. More on that later.
By default, custom elements inherit from HTMLElement
. Thus, the previous example is equivalent to:
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype)
});
A call to document.registerElement('x-foo')
teaches the browser about the new element, and returns a constructor that you can use to create instances of <x-foo>
. Alternatively, you can use the other techniques of instantiating elements if you don't want to use the constructor.
Extending elements #
Custom elements allows you to extend existing (native) HTML elements as well as other custom elements. To extend an element, you need to pass registerElement()
the name and prototype
of the element to inherit from.
Extending native elements #
Say you aren't happy with Regular Joe <button>
. You'd like to supercharge its capabilities to be a "Mega Button". To extend the <button>
element, create a new element that inherits the prototype
of HTMLButtonElement
and extends
the name of the element. In this case, "button":
var MegaButton = document.registerElement('mega-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
Custom elements that inherit from native elements are called type extension custom elements. They inherit from a specialized version of HTMLElement
as a way to say, "element X is a Y".
Example:
<button is="mega-button">
Extending a custom element #
To create an <x-foo-extended>
element that extends the <x-foo>
custom element, simply inherit its prototype and say what tag you're inheriting from:
var XFooProto = Object.create(HTMLElement.prototype);
...
var XFooExtended = document.registerElement('x-foo-extended', {
prototype: XFooProto,
extends: 'x-foo'
});
See Adding JS properties and methods below for more information on creating element prototypes.
How elements are upgraded #
Have you ever wondered why the HTML parser doesn't throw a fit on non-standard tags? For example, it's perfectly happy if we declare <randomtag>
on the page. According to the HTML specification:
Sorry <randomtag>
! You're non-standard and inherit from HTMLUnknownElement
.
The same is not true for custom elements. Elements with valid custom element names inherit from HTMLElement
. You can verify this fact by firing up the Console: Ctrl + Shift + J
(or Cmd + Opt + J
on Mac), and paste in the following lines of code; they return true
:
// "tabs" is not a valid custom element name
document.createElement('tabs').__proto__ === HTMLUnknownElement.prototype
// "x-tabs" is a valid custom element name
document.createElement('x-tabs').__proto__ == HTMLElement.prototype
Unresolved elements #
Because custom elements are registered by script using document.registerElement()
, they can be declared or created before their definition is registered by the browser. For example, you can declare <x-tabs>
on the page but end up invoking document.registerElement('x-tabs')
much later.
Before elements are upgraded to their definition, they're called unresolved elements. These are HTML elements that have a valid custom element name but haven't been registered.
This table might help keep things straight:
Name | Inherits from | Examples | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Unresolved element | `HTMLElement` | `Unknown element | `HTMLUnknownElement` | ` |
Example: defining All of the lifecycle callbacks are optional, but define them if/when it makes sense. For example, say your element is sufficiently complex and opens a connection to IndexedDB in Another use case lifecycle callbacks is for setting up default event listeners on the element: Adding markup #We've created Lifecycle callbacks come in handy here. Particularly, we can use Instantiating this tag and inspecting in the DevTools (right-click, select Inspect Element) should show: Encapsulating the internals in Shadow DOM #By itself, Shadow DOM is a powerful tool for encapsulating content. Use it in conjunction with custom elements and things get magical! Shadow DOM gives custom elements:
Creating an element from Shadow DOM is like creating one that renders basic markup. The difference is in Instead of setting the element's That's the Shadow Root! Creating elements from a template #HTML Templates are another new API primitive that fits nicely into the world of custom elements. Example: registering an element created from a These few lines of code pack a lot of punch. Let's understanding everything that is happening:
So good! Styling custom elements #As with any HTML tag, users of your custom tag can style it with selectors: Styling elements that use Shadow DOM #The rabbit hole goes much much deeper when you bring Shadow DOM into the mix. Custom elements that use Shadow DOM inherit its great benefits. Shadow DOM infuses an element with style encapsulation. Styles defined in a Shadow Root don't leak out of the host and don't bleed in from the page. In the case of a custom element, the element itself is the host. The properties of style encapsulation also allow custom elements to define default styles for themselves. Shadow DOM styling is a huge topic! If you want to learn more about it, I recommend a few of my other articles:
FOUC prevention using :unresolved #To mitigate FOUC, custom elements spec out a new CSS pseudo class, Example: fade in "x-foo" tags when they're registered: Keep in mind that For more on History and browser support #Feature detection #Feature detecting is a matter of checking if Browser support #
Until browser support is stellar, there's a polyfill which is used by Google's Polymer and Mozilla's X-Tag. What happened to HTMLElementElement? #For those that have followed the standardization work, you know there was once Unfortunately, there were too many timing issues with the upgrade process, corner cases, and Armageddon-like scenarios to work it all out. It's worth noting that Polymer implements a declarative form of element registration with Conclusion #Custom elements give us the tool to extend HTML's vocabulary, teach it new tricks, and jump through the wormholes of the web platform. Combine them with the other new platform primitives like Shadow DOM and If you're interested in getting started with web components, I recommend checking out Polymer. It's got more than enough to get you going. Last updated: — Improve article |