JavaScript slotchange Event and Dynamic Slots
This article provides a comprehensive overview of the
slotchange event in JavaScript Web Components, explaining
how it works and how developers can detect and respond to dynamic slot
assignments within the Shadow DOM. Readers will learn the mechanics
behind the event, how to inspect projected nodes, and best practices for
managing dynamic content changes in custom elements.
What is the
slotchange Event?
The slotchange event is a native DOM event that fires on
an <slot> element inside a Shadow Root whenever the
nodes assigned to that slot change. This occurs when elements in the
light DOM that are projected into a slot are added, removed, or
swapped.
It is important to note that slotchange only triggers
when the direct assignment of nodes to the slot changes. It does not
track internal attribute or text changes inside those assigned
nodes.
How to Listen for
slotchange
To react to changes in slot content, attach an event listener
directly to the <slot> element inside your custom
element’s Shadow DOM:
class CustomCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<div class="card">
<slot name="header"></slot>
<slot></slot>
</div>
`;
const defaultSlot = shadow.querySelector('slot:not([name])');
const headerSlot = shadow.querySelector('slot[name="header"]');
defaultSlot.addEventListener('slotchange', (event) => {
this.handleSlotChange(event.target);
});
headerSlot.addEventListener('slotchange', (event) => {
this.handleSlotChange(event.target);
});
}
handleSlotChange(slot) {
const assignedElements = slot.assignedElements();
console.log(`Slot '${slot.name || 'default'}' updated:`, assignedElements);
}
}
customElements.define('custom-card', CustomCard);Inspecting Assigned Nodes
When the slotchange event fires, JavaScript can query
the current state of the slot using two primary methods:
slot.assignedElements(options): Returns an array of only the assigned Element nodes, ignoring whitespace and text nodes.slot.assignedNodes(options): Returns an array of all assigned nodes, including text and comment nodes.
Both methods accept an optional configuration object: *
{ flatten: true }: If set to true, the method
returns nodes assigned to nested slots or fallback content if no nodes
are explicitly assigned.
const elements = slot.assignedElements({ flatten: true });
if (elements.length === 0) {
console.log('No elements assigned; displaying fallback content.');
}Reacting to Dynamic Content Updates
JavaScript reacts automatically to dynamic DOM manipulations
performed on the light DOM. Whenever child elements are appended,
removed, or have their slot attribute modified, the browser
re-evaluates slot assignments and fires slotchange.
const card = document.querySelector('custom-card');
// Appending a new child triggers the default slot's slotchange event
const p = document.createElement('p');
p.textContent = 'Dynamic paragraph content';
card.appendChild(p);
// Changing the slot attribute moves the node and triggers slotchange on both slots
p.setAttribute('slot', 'header');Tracking Deep Changes Inside Assigned Elements
Because slotchange only triggers when the assigned
top-level nodes change, changes within the children of those assigned
nodes will not fire the event. To monitor deeper changes, combine
slotchange with a MutationObserver:
const slot = shadowRoot.querySelector('slot');
const observer = new MutationObserver((mutations) => {
console.log('Deep content change detected within assigned nodes');
});
slot.addEventListener('slotchange', () => {
// Disconnect existing observations
observer.disconnect();
// Observe all current assigned elements for child or character data changes
slot.assignedElements().forEach((element) => {
observer.observe(element, {
childList: true,
subtree: true,
characterData: true
});
});
});Using the slotchange event alongside standard DOM
querying methods ensures your Web Components remain decoupled, reactive,
and responsive to any consumer-driven updates.