Garden-Variety Custom Elements
work in progressAs an early adopter of custom elements, I always understood them to be fairly straightforward: Back then, they mostly saved me the trouble of manually re-initializing components after the DOM was updated by some other part of the system (think tabs being injected into the page). Thus I was never tempted to confuse them with rendering libraries or even heavyweight frameworks.
However, because technology doesn’t exist in a vacuum, culture and momentum can play an important role in how such things are perceived. So it’s perfectly understandable that it took a while for the concepts to catch on as people worked through preconceived notions; depending on your background, the idea and benefits of custom elements might not be immediately obvious.
I recently heard a suggestion that we might need an educational playground like Flexbox Froggy. I’m not aware of anything like that, but it’s a compelling idea: I frequently find myself explaining custom elements and web components in an ad-hoc fashion, so I could really use a concise resource to point to. Unfortunately, I don’t have the wherewithal to even approximate that interactive tutorial’s style or quality (not least because this field is less deterministic) – however, I can at least try summarizing the fundamentals; a different kind of primer, if you will.
It’s Just HTML
At their core, custom elements are just HTML elements with funny names. Authors can make up an element name and use it in their documents without asking for permission:
<p>I talked to <cool-person>Les</cool-person> the other day.</p>
By convention,
custom names
should use hyphens, mostly to avoid conflicts with standardized elements (both
current and future). Other than that, browsers and other HTML parsers basically
don’t care; they tolerate unknown elements and treat them much like <span>
s.
Of course this means there’s no inherent significance to custom elements: No styling, semantic value (e.g. for assistive technology like screen readers) or fancy behavior.
It’s Just CSS
Now that we know we can just invent elements, why not invent a use case too? Let’s say we want to write about our friends and decorate their names with emoji. After concluding there’s not a more suitable standard element in this case (though that’s always debatable), we might decide on something like this:
<p>
Last night I met up with <cool-person>Kim</cool-person>,
<cool-person>Nikhil</cool-person> and
<cool-person>Ines</cool-person>.
</p>
cool-person {
font-variant: small-caps;
}
cool-person::before {
content: "👤 ";
}
Upon seeing this, our friends revolt: They’re not empty silhouettes! Let’s allow them to choose their own emoji:
<p>
Last night I met up with
<cool-person avatar="🧑🔬">Kim</cool-person>,
<cool-person>Nikhil</cool-person> and
<cool-person avatar="🥽">Ines</cool-person>.
</p>
cool-person::before {
content: attr(avatar, "👤") " ";
}
So we can not just invent element names, we can do the same for attributes too!
(Though we must take care to avoid redefining
standard attributes
like id
or class
.)
It’s Just JavaScript
That’s all well and good, but typically we’d employ custom elements to attach some custom functionality. Let’s do something a little more elaborate here: A simplistic CSV viewer.
In the spirit of progressive enhancement, we start with something that’s useful for everyone:
<csv-viewer>
Lipsum, Cicero, 45 BCE
Hello World, Kernighan, 1972
</csv-viewer>
csv-viewer:not(:defined) {
white-space: pre;
}
:defined
kicks in as soon as there’s a JavaScript definition for our custom
element. Let’s add that:
class CSViewer extends HTMLElement {
connectedCallback() {
let rows = csv2rows(this.textContent.trim());
this.replaceChildren();
this.append(...rows);
}
}
customElements.define("csv-viewer", CSViewer);
Henceforth, a <csv-viewer>
element anywhere in our HTML will transform CSV
data within that element into something more legibile.
There’s a subtle gotcha here: connectedCallback
is invoked as soon as a
corresponding element’s opening tag appears in our document. So if our custom
element’s JavaScript definition has already been registered by the time such an
element appears, our CSV content
might not be there yet.
TODO:
- “life-cycle notifications”
disconnectedCallback
constructor
adoptedCallback
attributeChangedCallback
MutationObserver
addEventListener
Web Components
- Shadow DOM
<template>