Skip to content

slotchange Event

This lesson explains the slotchange event and how to react when slotted content changes in Web Components, including default slots, named slots, empty states, and dynamic updates.

What Is the slotchange Event?

The slotchange event fires on a <slot> element when the set of assigned nodes changes. It lets a Web Component react when consumers add, remove, or update slotted content in the light DOM.

This event is useful for empty states, layout updates, validation, accessibility adjustments, and any behavior that depends on what content was projected into the component.

Concept Description
Event Name slotchange
Target The <slot> element
When It Fires Assigned slotted nodes change
Related API assignedElements(), assignedNodes()
Common Use Empty states and dynamic slot-aware UI
Works With Default slots and named slots

Basic slotchange Example

class ContentPanel extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <section class="panel">
        <slot></slot>
      </section>
    `;

    this.slot = this.querySelector("slot");
    this.slot.addEventListener("slotchange", () => {
      this.updateEmptyState();
    });

    this.updateEmptyState();
  }

  updateEmptyState() {
    const hasContent =
      this.slot.assignedElements().length > 0;

    this.classList.toggle("is-empty", !hasContent);
  }
}

customElements.define("content-panel", ContentPanel);
<content-panel>
  <p>You have 3 unread messages.</p>
</content-panel>

The component listens for slotchange and toggles an empty-state class based on whether any elements are assigned to the slot.

How slotchange Works

Step What Happens
1. Consumer updates light DOM Child nodes are added, removed, or reassigned
2. Browser recalculates assignment Nodes are matched to default or named slots
3. slotchange fires The affected <slot> dispatches the event
4. Component reads assigned content Use assignedElements() or assignedNodes()
5. UI updates Layout, classes, or accessibility attributes change

Reading Assigned Slot Content

handleSlotChange(event) {
  const slot = event.target;
  const elements = slot.assignedElements();
  const nodes = slot.assignedNodes();

  console.log("Elements:", elements.length);
  console.log("Nodes:", nodes.length);

  elements.forEach((element) => {
    console.log(element.tagName, element.textContent.trim());
  });
}
API Description
slot.assignedElements() Returns element nodes assigned to the slot
slot.assignedNodes() Returns all assigned nodes, including text nodes
event.target The slot that changed

slotchange with Named Slots

class AppCard extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <article class="card">
        <header><slot name="title"></slot></header>
        <div><slot></slot></div>
        <footer><slot name="footer"></slot></footer>
      </article>
    `;

    this.querySelectorAll("slot").forEach((slot) => {
      slot.addEventListener("slotchange", () => {
        this.updateSlotState(slot);
      });
    });
  }

  updateSlotState(slot) {
    const isEmpty =
      slot.assignedElements().length === 0;
    slot.parentElement.classList.toggle(
      "is-empty",
      isEmpty
    );
  }
}
<app-card>
  <h2 slot="title">Billing</h2>
  <p>Your plan renews tomorrow.</p>
  <button slot="footer" type="button">Manage Plan</button>
</app-card>

Components with multiple named slots often attach a slotchange listener to each slot so header, body, and footer regions can update independently.

slotchange in Shadow DOM

class EmptyState extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.innerHTML = `
      <style>
        :host(.is-empty) .placeholder {
          display: block;
        }
        .placeholder {
          display: none;
          color: #64748b;
        }
      </style>
      <section>
        <slot></slot>
        <p class="placeholder">Nothing to show yet.</p>
      </section>
    `;
  }

  connectedCallback() {
    const slot = this.shadowRoot.querySelector("slot");
    slot.addEventListener("slotchange", () => {
      const hasContent =
        slot.assignedElements().length > 0;
      this.classList.toggle("is-empty", !hasContent);
    });
  }
}

Shadow DOM does not prevent slotchange. The event still fires on the slot inside the shadow tree when light DOM content changes.

Reacting to Dynamic Slot Updates

const panel = document.querySelector("content-panel");

panel.innerHTML = `<p>First message</p>`;
// slotchange fires

panel.innerHTML = "";
// slotchange fires again

panel.innerHTML = `
  <p>Updated message</p>
  <button type="button">Dismiss</button>
`;
// slotchange fires again

Any change to assigned slotted content can trigger slotchange, including initial render, replacement, and removal of child nodes.

Run an Initial Slot Check

connectedCallback() {
  this.shadowRoot.innerHTML = `
    <section><slot></slot></section>
  `;

  this.slot = this.shadowRoot.querySelector("slot");
  this.slot.addEventListener("slotchange", () => {
    this.syncState();
  });

  // Important: slotchange may not fire on first connect
  this.syncState();
}

Do not rely only on the event for initial rendering. Call your slot-reading logic once during connectedCallback() so the component state is correct before any later changes occur.

Accessibility Updates with slotchange

updateAccessibility(slot) {
  const assigned = slot.assignedElements();
  const host = slot.getRootNode().host;

  if (assigned.length === 0) {
    host.setAttribute("aria-hidden", "true");
    return;
  }

  host.removeAttribute("aria-hidden");

  const heading = assigned.find(
    (node) => /^H[1-6]$/.test(node.tagName)
  );

  if (heading && !heading.id) {
    heading.id = `${host.localName}-title`;
  }

  if (heading) {
    host.setAttribute("aria-labelledby", heading.id);
  }
}

Use slotchange to keep accessibility attributes in sync when titles, descriptions, or actionable content appear or disappear from slotted regions.

When slotchange Fires

Action Fires slotchange?
Add slotted child element Yes
Remove slotted child element Yes
Change slot="name" assignment Yes
Update text inside slotted element No
Change attribute on slotted element No
Initial connect with stable slotted content Not guaranteed — run manual sync too

slotchange vs MutationObserver

Feature slotchange MutationObserver
Purpose Slot assignment changes Any DOM mutation
Complexity Simple and slot-specific More general and verbose
Best For Empty states and slot-aware layout Broader DOM watching needs
Performance Focused on slot updates Can fire for many unrelated changes

When to Use slotchange

  • You need empty-state behavior when no content is slotted.
  • Layout changes depend on which slot received content.
  • Accessibility labels must update when slotted titles change.
  • You want to validate or count assigned slot content.
  • Named slots need independent header, body, or footer logic.
  • Dynamic consumer content is added after initial render.

Common slotchange Use Cases

  • Cards that hide footer areas when no footer slot content exists.
  • Panels that show placeholder text for empty content regions.
  • Tabs that recalculate active content when slotted panels change.
  • Dialogs that set aria-labelledby from slotted titles.
  • Lists that count slotted items for badges or summaries.
  • Layout shells that adjust spacing based on assigned sections.

slotchange Best Practices

  • Attach listeners to the actual <slot> elements.
  • Run an initial sync in connectedCallback().
  • Use assignedElements() for element-based logic.
  • Handle each named slot separately when needed.
  • Keep slotchange handlers focused and lightweight.
  • Do not use slotchange for text or attribute edits inside slotted nodes.
  • Remove listeners in disconnectedCallback() when appropriate.

Common slotchange Mistakes

  • Assuming slotchange always fires on first render.
  • Listening on the host element instead of the slot.
  • Using slotchange to detect text changes inside slotted content.
  • Forgetting to handle multiple named slots independently.
  • Not checking for empty assigned content before updating UI.
  • Performing expensive work on every slotchange without need.
  • Using MutationObserver when slotchange is the simpler tool.

Key Takeaways

  • slotchange fires when assigned slotted nodes change.
  • Use it to react to added, removed, or reassigned slot content.
  • assignedElements() and assignedNodes() read slot content.
  • Run an initial slot sync during component setup.
  • slotchange is essential for dynamic, slot-aware Web Components.

Pro Tip

Combine slotchange with a one-time initial check in connectedCallback(). That gives you reliable empty states and slot-aware layout both on first render and after later content updates.