1 min read

Building a zero-dependency web component from scratch

Why reach for a library when the platform already has everything you need?

The first time I read the Custom Elements spec I closed the tab immediately. It felt like ceremony for something that should be simple. Six months later I came back, and something clicked.

The key insight

A custom element is just a class. You register it, the browser instantiates it when it sees your tag, and connectedCallback fires when it lands in the DOM.

class ReadingTime extends HTMLElement {
  connectedCallback() {
    const words = this.closest('article')
      ?.textContent.split(/\s+/).length ?? 0;
    this.textContent = ${</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Math</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ceil</span><span style="color:#032F62;--shiki-dark:#9ECBFF">(</span><span style="color:#24292E;--shiki-dark:#E1E4E8">words</span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 200</span><span style="color:#032F62;--shiki-dark:#9ECBFF">)</span><span style="color:#032F62;--shiki-dark:#9ECBFF">} min read;
  }
}

customElements.define('reading-time', ReadingTime);

That's it. Drop <reading-time></reading-time> anywhere near an article and it self-populates.

Why this matters

The platform ships features you never reach for because you assume they aren't there yet.

Once you internalize that, a lot of framework code starts looking like work you're doing for the browser, not with it.