DEV Community

Cover image for Web Components using UnknownHTMLElements for better semantic HTML
Danny Engelman
Danny Engelman

Posted on • Updated on


Web Components using UnknownHTMLElements for better semantic HTML

🥧 TL;DR;

  • UnknownHTMLElement elements can be used as Web Component building blocks
  • The <pie-chart> and <progress-circle> Web Components, used in this post are available on

<slice> is an UnknownHTMLElement

In my previous post
I explained how to build a vanilla JavaScript <pie-chart> Web Component.

With semantic HTML creating a Pie Chart:

  <slice size="90" stroke="green">HTML</slice>
  <slice size="1"  stroke="red">JavaScript</slice>
  <slice size="9"  stroke="blue">CSS</slice>
Enter fullscreen mode Exit fullscreen mode

Experienced Web Developers commented <slice> is not a valid HTML Element.

document.querySelector("slice") === "HTMLUnknownElement"
Enter fullscreen mode Exit fullscreen mode

Neither is it a valid Custom Element, because those always require at least one hyphen in the tagname.

That hyphen restriction is to prevent conflicts with any HTML tags the WHATWG might add in the future.

Custom Element tag names:

  • have to start with a lowercase ASCII character
  • can't have uppercase characters anywhere
  • must contain at least one hyphen - character
  • UTF-8 characters, emojis are allowed: <a-🥧-chart>
  • cannot be self closing: <pie-chart/>

What is the value of using <slice> ?

<pie-chart> must be a unique Custom Element (extended from HTMLElement)

  • Scoped registries have been on the agenda for 4 years now.
    For now all Custom Elements go into the same global customElementsRegistry

  • That means customElements.define("pie-chart") can only be called once.

  • If an element with the same tag name was already created,
    my <pie-chart> element will throw an error:

  • For a good UX (User eXperience) and DX (Developer eXperience) my <pie-chart> Web Component should test if it can be defined:
if (customElements.get("pie-chart")) {
    // warn the user "my" Web Component doesn't work; another definition exists
    // warn the developer another Web Component with the same name exists
} else {
    customElements.define("pie-chart", ... )
Enter fullscreen mode Exit fullscreen mode

To <slice> or to <pie-slice>, that is the question

<slice> has less restrictions

  • A duplicate <slice> can only be created by the WHATWG adding <slice> as standard HTML tag.

  • Till then. I know with 100% certainty there will not be a naming conflict.

  • Because no 3rd party Developer can create a valid <slice> element:

  • Had I used a <pie-slice> Custom Element,
    it would have been an additional point of failure.
    I would have to add extra code to check if <pie-slice> was defined by another developer; like required for <pie-chart>

<pie-slice> versus <slice>

  • <pie-slice> can have all the Web Components goodies: observedAttributes , attributeChangedCallback etc.

  • <slice> will always be an UnknownHTMLElement.
    observedAttributes-like behaviour can only be created by adding a MutationObserver (yes, works on Unknown Elements)
    You then get an extra bonus; because a MutationObserver can observe text Node changes.
    A Custom Element can not monitor the innerHTML label.

(ab)using existing HTML tags

We can test the "WHATWG created a new HTML tag" scenario.

<progress> is an existing HTML tag

And for those who don't know.
Many standard HTML elements, <input> , <video>, are Web Components
Browsers have different implementations;
check out the <progress> documentation page in Chrome, Firefox or Safari

Similar to <pie-chart> (and using the same Base Class, available on

This chart (modelled after a Google Analytics chart):

is created by the <progress-circle> Web Component, from semantic HTML:

  <progress value="75%" stroke="green">SEO</progress>
  <progress value="60%" stroke="orange">Social</progress>
  <progress value="65%" stroke="teal" edge="black">Maps</progress>
  <progress value="50%" stroke="orangered">Traffic</progress>
Enter fullscreen mode Exit fullscreen mode
  • This Web Component uses shadowDOM to display the chart, the semantic HTML remains invisible in lightDOM

  • Thus you do not see the <progress> default behaviour

  • If you really, really want a "NO JavaScript!" version, you have to change the HTML, CSS (and Web Component code) a bit to work with something like this:

<progress max="100" value="75" stroke="green" label="SEO"></progress>
<progress max="100" value="60" stroke="orange" label="Social"></progress>
<progress max="100" value="65" stroke="teal" label="Maps"></progress>
<progress max="100" value="50" stroke="orangered" label="Traffic">Traffic</progress>
Enter fullscreen mode Exit fullscreen mode
  • again, the <progress> elements remains invisible in lightDOM

  • the resulting chart is displayed in shadowDOM

  • I only have to safeguard a duplicate naming conflict for the <progress-circle> Web Component.

  • As long as you can keep the output in invisible lightDOM,
    you can (ab)use any existing HTML tag for other purposes.

The <pie-chart> and <progress-circle> Web Components are available, as unlicensed source code, on

The code is an enhanced version of my previous <pie-chart> post

  • instead of a SVG <circle> Element, a <path> Element is used to draw slices. The <progress-circle> can not be created with <circle> elements.

  • Uses shadowDOM

  • uses ::part shadowParts for optional global styling

  • all in vanilla JavaScript, (way) under 2 kB GZipped

  • a starting point for you own Web Components

Top comments (0)

This post blew up on DEV in 2020:

js visualized

🚀⚙️ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! 🥳

Happy coding!