Skip to content
⚠️ This article was written in 2020. Some content may be outdated.

Web Components: Custom Elements and Shadow DOM in Practice

Web Components are the browser's native componentization approach — you can create reusable custom HTML elements without any framework. By 2020 all major browsers support them, so now is a good time to dig in.

The Three Core APIs ​

Web Components = Custom Elements + Shadow DOM + HTML Templates
  • Custom Elements: define custom HTML tags (<my-button>)
  • Shadow DOM: an isolated DOM subtree whose styles don't leak out and aren't affected from outside
  • HTML Templates: the <template> tag, a lazily parsed template

Creating Your First Custom Element ​

javascript
class UserCard extends HTMLElement {
  constructor() {
    super();
    // 创建 Shadow DOM
    const shadow = this.attachShadow({ mode: "open" });

    shadow.innerHTML = `
      <style>
        :host {
          display: block;
          border: 1px solid #ddd;
          border-radius: 8px;
          padding: 16px;
          font-family: sans-serif;
        }
        .name { font-size: 18px; font-weight: bold; }
        .email { color: #666; font-size: 14px; }
        button {
          background: #0066ff;
          color: white;
          border: none;
          border-radius: 4px;
          padding: 6px 12px;
          cursor: pointer;
        }
      </style>
      <div class="name"></div>
      <div class="email"></div>
      <button>关注</button>
    `;
  }

  // 监听的 attribute 列表
  static get observedAttributes() {
    return ["name", "email"];
  }

  // attribute 变化时触发
  attributeChangedCallback(name, oldValue, newValue) {
    const shadow = this.shadowRoot;
    if (name === "name") {
      shadow.querySelector(".name").textContent = newValue;
    }
    if (name === "email") {
      shadow.querySelector(".email").textContent = newValue;
    }
  }

  // 生命周期:插入 DOM
  connectedCallback() {
    this.shadowRoot.querySelector("button").addEventListener("click", () => {
      this.dispatchEvent(
        new CustomEvent("follow", {
          detail: { name: this.getAttribute("name") },
          bubbles: true,
        }),
      );
    });
  }
}

customElements.define("user-card", UserCard);
html
<!-- 使用 -->
<user-card name="Alice" email="alice@example.com"></user-card>

<script>
  document.querySelector("user-card").addEventListener("follow", (e) => {
    console.log("关注了:", e.detail.name);
  });
</script>

Slots: Content Projection ​

Similar to Vue's <slot> or Angular's <ng-content>:

javascript
class MyCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" }).innerHTML = `
      <style>
        .card { border: 1px solid #eee; padding: 16px; }
        .header { background: #f5f5f5; padding: 8px; }
      </style>
      <div class="card">
        <div class="header">
          <slot name="title">默认标题</slot>
        </div>
        <slot></slot>  <!-- 默认 slot -->
      </div>
    `;
  }
}

customElements.define("my-card", MyCard);
html
<my-card>
  <h2 slot="title">自定义标题</h2>
  <p>这是正文内容,进入默认 slot</p>
</my-card>

Integrating with Frameworks ​

Custom Elements can be embedded seamlessly into any framework:

jsx
// React 中使用
function App() {
  return (
    <user-card
      name="Alice"
      email="alice@example.com"
      onFollow={(e) => console.log(e.detail)}
    />
  );
}
html
<!-- Vue 中使用 -->
<user-card :name="user.name" :email="user.email" @follow="handleFollow" />

When to Choose Web Components ​

Good fits:

  • Sharing UI components across frameworks (design systems)
  • Components that need strict style isolation (third-party embeds)
  • Component libraries that don't want to be tied to a specific framework

Less suitable:

  • Pure React/Vue/Angular projects (the framework's own component system is more convenient)
  • Cases needing strong TypeScript typing (Web Components' native TS support is weak)

Summary ​

The value of Web Components lies in their framework independence. More and more design systems (such as Adobe Spectrum and IBM Carbon) are shipping Web Components versions. Even if your day-to-day work stays inside Angular or React, understanding Web Components helps you design more universal component boundaries.

MIT Licensed