JavaScript Web Components Table

PieceWhat it doesField note
customElements.define(name, cls)Register a tagDash REQUIRED in the name (my-card) - reserved namespace for the platform
class extends HTMLElementThe element classconnectedCallback runs when added to DOM - the mount hook
attachShadow({ mode: 'open' })Encapsulated DOM treeopen = inspectable via element.shadowRoot; closed hides even that
<template>Inert HTML blueprintParses but renders nothing - clone it per instance
<slot>Light-DOM projectionChildren land here - named slots sort them by slot attribute
observedAttributes + attributeChangedCallbackReact to attributesOnly LISTED attributes fire the callback - the whitelist
:host / ::slottedStyle across the boundaryPage CSS cannot reach in; these selectors reach out
CustomEvent detailEmit outwarddispatchEvent with { detail } - the component's public events
Reference: the MDN Web Components reference. Web Components are the platform's own component model: a custom tag with encapsulated DOM (shadow root), its own lifecycle (connectedCallback, attributeChangedCallback) and a slot mechanism for projecting children - framework-shaped, but shipping in the browser with zero dependencies. The naming rule is the giveaway: the dash in my-card is RESERVED - platform tags never contain one, so collisions are impossible by grammar. Bottom line: shadow DOM gives style isolation (page CSS cannot reach in; :host and ::slotted reach out), template+clone is the cheap render path, and custom elements interop with every framework because they ARE elements. Related tools: semantic elements table (native elements before custom ones), DOM manipulation table (the DOM the shadow tree lives in), and event listeners table (events that cross - and fail to cross - the boundary).

Web Components are the platform's own component model: a custom tag with encapsulated DOM (shadow root), a lifecycle (connectedCallback, attributeChangedCallback), and slots for projecting children - framework-shaped, but native, dependency-free, and interoperable with every framework because the result IS an element.

Bottom line: the dash in the tag name is the contract - my-card is legal, card is not, because the platform reserved the dash-free namespace for itself, making collisions impossible by grammar. Shadow DOM is the style wall: page CSS cannot reach in; :host and ::slotted are the sanctioned reaches outward.

The honest part: closed shadow roots are not security - element.shadowRoot returns null but devtools and composed events still see through; the mode expresses intent, not privacy. And each custom element still costs the same DOM reality as any element: a thousand instances are a thousand nodes, shadow tree or not.

How to use

  1. Register once: customElements.define('user-card', class extends HTMLElement { connectedCallback() { ... } }) - the dash in the name is mandatory, the callback is your mount.
  2. Render through template: keep the HTML in a <template>, clone it per instance into the shadow root - parsed once, cloned cheaply, inert until used.
  3. Communicate like an element: attributes in (whitelist them in observedAttributes), CustomEvents with detail out - the same contract native elements follow.

Frequently asked questions

Why must custom element names contain a dash?

Namespace by grammar. The HTML specification reserves all dash-free tag names for the platform, present and future - if customElements.define('dialog') were legal, it would collide with the platform's own <dialog> and every future element the standards add. The dash makes user tags syntactically distinguishable: if it has a dash, it is somebody's custom element, guaranteed. The engine enforces this - define without a dash throws a DOMException - and the convention of a vendor prefix (acme-card) keeps third-party components from colliding with each other.

What does shadow DOM actually isolate - and what does it NOT?

It isolates CSS and DOM structure: page stylesheets cannot select into the shadow tree, and document.querySelector cannot see its nodes - styles defined inside stay inside, leaks in either direction stop at the boundary. It does NOT isolate JavaScript or events entirely: the light DOM can reach element.shadowRoot (when mode is open), events keep bubbling into the page (retargeted so the host appears as the source), and forms/focus still integrate. Closed mode hides the shadowRoot reference but is intent, not security - devtools still render it. Plan around isolation for styling; do not build security on it.

How do slots project light DOM children into the shadow tree?

Slots are placement holes: children written inside <user-card> in the normal light DOM get PROJECTED into matching <slot> positions of the shadow tree - default slot catches unnamed children, named slots catch slot="title" children. The key mental model: the elements still LIVE in the light DOM (document styles and queries find them there); slots only decide where they RENDER. That is why ::slotted(b) can style them only with inherited-safe properties - their real stylesheets still apply from the page side. Empty slots show their fallback content, which is how default labels work.

When are web components the right call over a framework component?

When the artifact must outlive frameworks or cross them: a design system of cards, inputs and icons consumed by teams on different stacks, or embeddable widgets dropped into any page (the dash-tag works in plain HTML, React, Vue, or CMS templates alike). Inside a single-framework app with no sharing needs, framework components are usually more ergonomic - better tooling, state handling, no boilerplate. The pragmatic split: platform components at the BOUNDARY (design systems, embeds, progressive enhancement of server-rendered pages), framework components in the interior where their ecosystem pays for itself.

Related tools