CustomEvent. A child component fires the event with a data payload, and an ancestor component catches it using an on{eventname} declarative listener or a programmatic DOM listener.
Building maintainable Salesforce applications requires clean component communication. LWC adheres to standard DOM events following the principle of "properties down, events up." Below are three complete code patterns demonstrating direct child-to-parent messaging, cross-boundary event bubbling, and stopping event propagation.
1. Direct Child-to-Parent Communication
The standard event model dispatches a non-bubbling event from a child component that is captured directly by its immediate parent template.
Child Component Markup (childComponent.html)
<template>
<lightning-button
label="Send Data to Parent"
variant="brand"
onclick={handleButtonClick}>
</lightning-button>
</template>
Child Component JavaScript (childComponent.js)
import { LightningElement } from 'lwc';
export default class ChildComponent extends LightningElement {
handleButtonClick() {
// Create custom event with payload in detail property
const selectEvent = new CustomEvent('customevent', {
detail: { message: 'Data emitted directly from child!' }
});
this.dispatchEvent(selectEvent);
}
}
Parent Component Markup (parentComponent.html)
<template>
<lightning-card title="Parent Container" icon-name="standard:account">
<div class="slds-p-around_medium">
<!-- Listen using 'oncustomevent' (prefix 'on' + lowercase event name) -->
<c-child-component oncustomevent={handleCustomEvent}></c-child-component>
<template lwc:if={receivedMessage}>
<p class="slds-m-top_medium slds-text-color_success">
Received: <strong>{receivedMessage}</strong>
</p>
</template>
</div>
</lightning-card>
</template>
Parent Component JavaScript (parentComponent.js)
import { LightningElement } from 'lwc';
export default class ParentComponent extends LightningElement {
receivedMessage = '';
handleCustomEvent(event) {
this.receivedMessage = event.detail.message;
console.log('Event received in parent:', event.detail);
}
}
2. Component Event Bubbling Across Shadow Boundaries
When an event needs to travel past the direct parent to higher ancestor components (like a grandparent container), set bubbles: true and composed: true.
{ bubbles: false, composed: false }(Default): Fired on child; caught only by the direct parent template.{ bubbles: true, composed: false }: Bubbles up the internal DOM tree, but stops at the Shadow DOM boundary.{ bubbles: true, composed: true }: Crosses Shadow roots and bubbles up through ancestor component hierarchies.
Child Dispatcher (bubbleChild.js)
import { LightningElement } from 'lwc';
export default class BubbleChild extends LightningElement {
fireBubblingEvent() {
const bubbleEvent = new CustomEvent('grandparentevent', {
bubbles: true,
composed: true,
detail: { recordId: '001xx000003DGSZAA4', origin: 'Deep Nested Child' }
});
this.dispatchEvent(bubbleEvent);
}
}
Grandparent Listener Markup (grandParentComponent.html)
<template>
<div class="slds-box" ongrandparentevent={handleGrandParentEvent}>
<p>Grandparent Container</p>
<c-parent-component></c-parent-component>
</div>
</template>
3. Intercepting & Stopping Event Propagation
An intermediate component can intercept a bubbling event and prevent it from reaching ancestors higher in the hierarchy using event.stopPropagation().
Intermediate Parent JavaScript (parentComponent.js)
import { LightningElement } from 'lwc';
export default class ParentComponent extends LightningElement {
handleChildEvent(event) {
console.log('Handled in parent; blocking grandparent notification.');
// Stops the event from bubbling further up to the grandparent
event.stopPropagation();
}
}
4. Common Traps & Event Best Practices
Never name custom events using uppercase letters or manual 'on' prefixes (e.g.,
new CustomEvent('onSelectRecord')). The LWC template compiler requires all lowercase, single-word event names (e.g., selectrecord) and automatically prepends on in the template binding (onselectrecord={handler}).
- Pass Serialized Primitives: Send IDs or clean payload objects inside
detailrather than complex class instances or live DOM element references. - Decouple with LMS: For communication between completely unrelated components in separate DOM branches or across Aura/Visualforce boundaries, use Lightning Message Service (LMS) instead of global bubbling events.
- Prevent Default Behaviors: Use
event.preventDefault()when intercepting standard browser form submissions or hyperlink navigations.
Summary
Mastering LWC event communication ensures that your Salesforce applications remain modular, decoupled, and performant. By employing standard CustomEvent patterns, configuring bubbles and composed flags deliberately, and managing propagation with event.stopPropagation(), you can build scalable multi-tiered component hierarchies.