Skip to content

Web Components API Reference

This lesson is a practical API reference for Web Components standards, covering Custom Elements, Shadow DOM, templates, slots, events, and styling hooks with examples and quick lookup tables.

Web Components APIs at a Glance

Web Components are built on browser-native APIs. This reference summarizes the most important interfaces and methods you use when creating, styling, composing, and communicating with custom elements in production projects.

Standard Main API Purpose
Custom Elements customElements, HTMLElement Define and upgrade custom tags
Shadow DOM attachShadow(), shadowRoot Encapsulate internal DOM and CSS
HTML Templates <template> Store reusable inert markup
Slots <slot> Project light DOM content
Events CustomEvent Emit component output
Forms ElementInternals Participate in forms and validation

Custom Elements API

class AppBadge extends HTMLElement {
  static get observedAttributes() {
    return ["variant"];
  }

  constructor() {
    super();
  }

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue !== newValue) this.render();
  }
}

customElements.define("app-badge", AppBadge);
API Description
customElements.define(name, class) Registers a custom element class
customElements.get(name) Returns the constructor for a defined element
customElements.whenDefined(name) Promise that resolves when the element is defined
customElements.upgrade(root) Upgrades undefined custom elements in a tree
HTMLElement.observedAttributes Static list of attributes to watch
connectedCallback() Called when element is added to the DOM
disconnectedCallback() Called when element is removed from the DOM
attributeChangedCallback() Called when an observed attribute changes
adoptedCallback() Called when element moves to a new document

Attributes and Properties API

class ToggleSwitch extends HTMLElement {
  static get observedAttributes() {
    return ["checked"];
  }

  get checked() {
    return this.hasAttribute("checked");
  }

  set checked(value) {
    this.toggleAttribute("checked", Boolean(value));
  }

  attributeChangedCallback(name) {
    if (name === "checked") {
      this.dispatchEvent(
        new Event("change", { bubbles: true })
      );
    }
  }
}
Method / Property Description
getAttribute(name) Read an attribute value as a string
setAttribute(name, value) Set or update an attribute
hasAttribute(name) Check whether an attribute exists
removeAttribute(name) Remove an attribute
toggleAttribute(name, force) Add or remove a boolean attribute
element.property JavaScript property API for complex values

Use attributes for declarative HTML configuration and properties for rich JavaScript values such as objects, arrays, and functions.

Shadow DOM API

class ProfileCard extends HTMLElement {
  constructor() {
    super();
    this.shadow =
      this.attachShadow({ mode: "open" });

    this.shadow.innerHTML = `
      <style>
        :host { display: block; }
        .card { padding: 1rem; border: 1px solid #ccc; }
      </style>
      <article class="card" part="card">
        <slot name="title"></slot>
        <slot></slot>
      </article>
    `;
  }
}
API Description
element.attachShadow({ mode }) Creates a shadow root on the host element
element.shadowRoot Returns the open shadow root, if available
mode: "open" Shadow root accessible via shadowRoot
mode: "closed" Shadow root hidden from outside access
:host Styles the custom element host from inside shadow CSS
::slotted() Styles content assigned to a slot
part / ::part() Expose selected internal elements for styling

HTML Template API

<template id="user-row-template">
  <article class="row">
    <strong><slot name="name"></slot></strong>
    <span><slot name="role"></slot></span>
  </article>
</template>
class UserRow extends HTMLElement {
  connectedCallback() {
    const template =
      document.getElementById("user-row-template");
    const content =
      template.content.cloneNode(true);

    this.appendChild(content);
  }
}
API Description
<template> Holds inert markup that is not rendered immediately
template.content DocumentFragment containing template children
cloneNode(true) Creates a reusable copy of template content

Slots API

<info-card>
  <h2 slot="title">Billing</h2>
  <p>Your plan renews tomorrow.</p>
  <button slot="footer">Manage</button>
</info-card>
class InfoCard extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <section>
        <header><slot name="title"></slot></header>
        <div><slot></slot></div>
        <footer><slot name="footer"></slot></footer>
      </section>
    `;

    this.querySelector("slot").addEventListener(
      "slotchange",
      () => this.handleSlotChange()
    );
  }
}
API Description
<slot> Default slot for projected child content
<slot name="..."> Named slot for targeted content
slot="name" Assigns an element to a named slot
slot.assignedElements() Returns elements assigned to a slot
slotchange Event fired when slotted content changes

Custom Events API

this.dispatchEvent(
  new CustomEvent("item-selected", {
    detail: {
      id: this.itemId,
      label: this.label
    },
    bubbles: true,
    composed: true,
    cancelable: true
  })
);
Option Description
detail Payload attached to the event object
bubbles: true Event propagates up the DOM tree
composed: true Event crosses Shadow DOM boundaries
cancelable: true Event can be canceled with preventDefault()
event.detail Data read by parent app listeners

ElementInternals API

class AppTextInput extends HTMLElement {
  static formAssociated = true;

  constructor() {
    super();
    this.internals = this.attachInternals();
  }

  connectedCallback() {
    this.innerHTML = `<input type="text" />`;
    this.input = this.querySelector("input");
    this.input.addEventListener("input", () => {
      this.internals.setFormValue(this.input.value);
    });
  }
}
API Description
static formAssociated = true Marks the element as form-associated
attachInternals() Gets the ElementInternals object
internals.setFormValue(value) Sets the control value for form submission
internals.setValidity() Reports validation state to the form
internals.form Returns the associated form, if any

Styling APIs

app-button {
  --btn-bg: #2563eb;
  --btn-color: white;
}

app-button::part(button) {
  border-radius: 0.5rem;
}
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
  button { background: var(--btn-bg, #111); }
`);

this.shadowRoot.adoptedStyleSheets = [sheet];
API Description
CSS custom properties Theme components from outside the shadow tree
part attribute Marks internal elements for external styling
::part(name) Styles exposed internal parts from outside
adoptedStyleSheets Shares constructable stylesheets in Shadow DOM
:host-context() Applies styles based on outer context

Declarative Shadow DOM

<profile-card>
  <template shadowrootmode="open">
    <style>
      :host { display: block; }
    </style>
    <article><slot></slot></article>
  </template>
  <p>Alex Chen</p>
</profile-card>

Declarative Shadow DOM lets you define a shadow tree directly in HTML. This is useful for server-side rendering and progressive enhancement without waiting for JavaScript to attach a shadow root.

Quick Reference by Task

Task API to Use
Register a custom tag customElements.define()
React to DOM insertion connectedCallback()
Watch attribute changes observedAttributes + attributeChangedCallback()
Encapsulate markup and CSS attachShadow()
Accept external content <slot>
Notify parent apps CustomEvent
Participate in forms ElementInternals
Theme from outside CSS custom properties and ::part()

When to Use Each API

  • Use customElements.define() for every reusable custom tag.
  • Use Shadow DOM when internal structure and CSS should stay private.
  • Use templates when markup should be cloned instead of rebuilt as strings.
  • Use slots when consumers need flexible composition.
  • Use custom events when parent apps need output from a component.
  • Use ElementInternals for custom form controls and validation.
  • Use CSS parts and custom properties when theming must stay controlled.

API Usage Best Practices

  • Keep the public API small: attributes, properties, events, and slots.
  • Document every observed attribute and emitted event.
  • Prefer open shadow roots unless strong encapsulation is required.
  • Use composed: true only when parent apps must hear the event.
  • Sync attributes and properties predictably for framework consumers.
  • Clean up listeners and timers in disconnectedCallback().
  • Expose styling hooks intentionally instead of leaking all internals.

Common API Mistakes

  • Calling attachShadow() more than once on the same element.
  • Forgetting to declare observedAttributes before watching changes.
  • Using attributes for object values instead of properties.
  • Emitting events without documenting names and detail shape.
  • Overusing closed Shadow DOM and breaking testing or theming.
  • Not using composed: true for events that must leave Shadow DOM.
  • Exposing too many internal parts and defeating encapsulation.

Key Takeaways

  • Web Components are powered by a small set of browser-native APIs.
  • Custom Elements handle behavior, Shadow DOM handles encapsulation.
  • Templates and slots enable reusable and composable markup.
  • Custom events and ElementInternals extend the public contract.
  • This reference is the foundation for building production-ready components.

Pro Tip

When documenting a component library, list its API in four sections: attributes, properties, events, and slots. That matches how developers actually consume Web Components in HTML and JavaScript.