Skip to main content

Command Palette

Search for a command to run...

Component Crusaders

“A Journey Through Shadow DOM, Slots, and the Art of Reusable Components”

Published
4 min readView as Markdown

When I first started building my portfolio, I wanted something modern, reusable, and framework-agnostic. That’s when I discovered Web Components.

Web Components let you create custom HTML elements that encapsulate structure, styling, and behavior — all reusable across projects. Unlike frameworks, they’re native to the browser, lightweight, and framework-independent.


⚙️ Why I Chose Web Components

While frameworks like React or Vue offer reusable components and scoped CSS (via module.scss), Web Components offer true runtime encapsulation through Shadow DOM:

  • Encapsulation – Shadow DOM ensures styles and markup don’t leak in or out.

  • Reusability – Once built, components can be dropped into any project or page.

  • Framework-agnostic – They work anywhere, even alongside React, Vue, or vanilla JS.

  • Native performance – No extra runtime overhead; the browser handles them natively.

Tip: CSS Modules in React only scope class names at build time, but Shadow DOM isolates the entire DOM subtree at runtime.


🧩 Defining a Custom Element

A Custom Element is a new HTML tag you define, e.g., <portfolio-card>.

class PortfolioCard extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });

    shadow.innerHTML = `
      <style>
        .card {
          border: 1px solid #ccc;
          padding: 1rem;
          border-radius: 0.5rem;
          transition: box-shadow 0.3s;
        }
        .card:hover {
          box-shadow: 0 4px 12px rgba(0,0,0,0.15);
        }
      </style>
      <div class="card">
        <slot></slot>
      </div>
    `;
  }

  connectedCallback() {
    console.log('PortfolioCard added to the DOM');
  }

  disconnectedCallback() {
    console.log('PortfolioCard removed from the DOM');
  }

  adoptedCallback() {
    console.log('PortfolioCard moved to a new document');
  }

  attributeChangedCallback(name, oldValue, newValue) {
    console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
  }

  static get observedAttributes() {
    return ['data-title']; // watch changes for this attribute
  }
}

customElements.define('portfolio-card', PortfolioCard);

Lifecycle Callbacks Explained

  • connectedCallback() – Called when the element is added to the DOM.

  • disconnectedCallback() – Called when the element is removed from the DOM.

  • adoptedCallback() – Called when the element is moved to a new document.

  • attributeChangedCallback(name, oldValue, newValue) – Called when an observed attribute changes.

These methods let you react to changes, similar to React hooks (useEffect) but at the native browser level.


🔄 Using Slots: Injecting Content

<portfolio-card>
  <h2>My Awesome Project</h2>
  <p>This project demonstrates Web Components in action!</p>
</portfolio-card>
  • The <slot> inside <portfolio-card> receives <h2> and <p> from outside.

  • Shadow DOM ensures the component’s styles encapsulate the content without affecting the rest of the page.

Named slots:

shadow.innerHTML = `
  <div class="header"><slot name="header"></slot></div>
  <div class="body"><slot name="body"></slot></div>
`;
<portfolio-card>
  <span slot="header">Project Title</span>
  <span slot="body">This is a description of the project.</span>
</portfolio-card>

🔍 Querying and Manipulating Shadow DOM

You can interact with elements inside Shadow DOM using the shadowRoot:

class PortfolioCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' }).innerHTML = `
      <div class="card">
        <p class="content">Initial content</p>
        <button id="btn">Click me</button>
      </div>
    `;
  }

  connectedCallback() {
    const btn = this.shadowRoot.querySelector('#btn');
    const content = this.shadowRoot.querySelector('.content');

    btn.addEventListener('click', () => {
      content.textContent = 'Content updated via Shadow DOM!';
    });
  }
}
customElements.define('portfolio-card', PortfolioCard);

✅ You can query, modify, and append elements inside Shadow DOM just like normal DOM, but outside styles or JS cannot affect it unless you explicitly expose it.


⚡ Benefits I Experienced

  • Cleaner Codebase – Each UI block is self-contained.

  • Reusable Across Projects – Drop components anywhere, no extra framework required.

  • Future-Proof – Works independently of frameworks.

  • Better Understanding of Browser – Deepens knowledge of native DOM, events, and rendering.


⚠️ Challenges with Web Components

  1. Browser Support – Modern browsers support Web Components, older ones need polyfills.

  2. Styling Shadow DOM – Global styles don’t penetrate, and you can’t style deep elements outside.

    • Solution: Use CSS variables for theming, or pass styles via ::part and ::theme.
  3. State Management – Unlike React, you need to manually handle updates inside the component.

    • Solution: Use observed attributes or internal JS state + lifecycle callbacks.
  4. Tooling and Debugging – Dev tools and IDE support are less mature.

    • Solution: Use Chrome/Firefox DevTools for inspecting Shadow DOM; consider small helper libraries if needed.
  5. Communication Between Components – Passing events or data between multiple custom elements can be tricky.

    • Solution: Use Custom Events or Shared State via global store.

🧠 Lessons Learned

  • Web Components excel in design systems, micro frontends, or static websites.

  • Shadow DOM gives true runtime isolation, beyond CSS Modules or framework scoping.

  • Slots make components dynamic and reusable.

  • Lifecycle callbacks allow native reactivity without any framework overhead.


💡 Takeaway

Web Components allow you to write encapsulated, reusable, and framework-independent components using native browser APIs.

By combining Custom Elements + Shadow DOM + Slots + Lifecycle Callbacks, you can build UI that’s maintainable, modular, and future-proof, even for personal projects like a portfolio.