Web Components

Course 3 · Ch 1
Web Components in Depth
The full custom element lifecycle, templates feeding the shadow root, and slots for user-supplied content

Intermediate Course Chapter 11 introduced the core concept — a custom element with a shadow root. This chapter completes the picture: the full lifecycle a custom element actually goes through, building it properly with <template> instead of a string literal, and <slot> for letting users place their own content inside your component.

The Full Custom Element Lifecycle

constructor connectedCallback attributeChangedCallback*
*fires repeatedly, any time a watched attribute changes — not just once like the others
CallbackFires when
constructor()The element instance is created — too early to safely read attributes or children yet
connectedCallback()The element is actually inserted into the document (Intermediate Ch11) — the safe place to read attributes and build initial content
disconnectedCallback()The element is removed from the document — clean up event listeners, timers, subscriptions here
attributeChangedCallback(name, old, new)A watched attribute's value changes — only fires for attributes explicitly listed in observedAttributes
class RatingStars extends HTMLElement { static get observedAttributes() { return ['value']; // only "value" triggers attributeChangedCallback } attributeChangedCallback(name, oldValue, newValue) { if (name === 'value') { this.render(newValue); } } disconnectedCallback() { // clean up anything set up in connectedCallback, if applicable } }
attributeChangedCallback only fires for attributes you explicitly list
Forgetting to add an attribute name to the static observedAttributes array means changes to it are silently ignored — the callback never fires for anything not in that list. A genuinely common first mistake when building a component that's supposed to react to attribute changes.

Using template with Shadow DOM — The Cleaner Pattern

Intermediate Chapter 11's example built shadow DOM content via a string template literal. A real component typically uses an actual <template> element (Intermediate Chapter 8) instead — genuine, validatable HTML rather than a string.

<template id="ratingStarsTemplate"> <style> .star { color: gold; } </style> <span class="star">★★★★★</span> </template> <script> class RatingStars extends HTMLElement { connectedCallback() { const template = document.getElementById('ratingStarsTemplate'); const shadow = this.attachShadow({ mode: 'open' }); shadow.appendChild(template.content.cloneNode(true)); } } customElements.define('rating-stars', RatingStars); </script>

slot — Letting Users Pass Their Own Content In

Without slots, a custom element's content is entirely fixed by its own internal template — there's no way for someone using the component to insert their own content into it. <slot> creates a placeholder the user's own light-DOM content gets projected into.

// Inside the component's shadow root template: <div class="card"> <h3>Card Title</h3> <slot></slot> // whatever the user puts inside <my-card> appears here </div> // Usage — this content gets projected into the slot above: <my-card> <p>This is the user's own content, slotted in.</p> </my-card>

Named Slots — Multiple Insertion Points

// Component template, with two distinct named slots: <div class="card"> <header><slot name="title"></slot></header> <slot></slot> // the unnamed default slot, for everything else </div> // Usage — slot="title" routes that specific element to the named slot above: <my-card> <h3 slot="title">Card Title</h3> <p>Goes into the default slot instead.</p> </my-card>
Slotted content keeps its original styling context, unlike the rest of the shadow root
Content placed into a slot is still technically "light DOM" — it's styled by the main page's CSS, not the component's internal shadow styles, even though it visually appears inside the component. This is a deliberate, useful exception to the otherwise strict shadow DOM encapsulation boundary from Intermediate Chapter 11.

Chapter 1 Quick Reference

  • Full lifecycle: constructor → connectedCallback → attributeChangedCallback (repeatable) → disconnectedCallback
  • observedAttributes must explicitly list every attribute that should trigger attributeChangedCallback
  • Build shadow content from a real <template>, cloned via cloneNode(true), rather than a string literal
  • <slot> — a placeholder for user-supplied light-DOM content to be projected into the shadow root
  • Named slots (slot="name") — multiple distinct insertion points within one component
  • Slotted content keeps light-DOM styling — a deliberate exception to shadow DOM's otherwise strict encapsulation
  • Next chapter: Canvas API basics — drawing, animation fundamentals