Skip to main content

LWC Event Communication Examples: Parent-Child, Bubbling & Propagation

In plain words: In Lightning Web Components (LWC), components communicate upwards by creating and dispatching a 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.

Pattern 1: Direct Parent-Child Event Wiring

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.

Event Propagation Configuration Card:
  • { 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.
Pattern 2: Dispathing a Composed Bubbling Event

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().

Pattern 3: Halting Event Propagation in Parent

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

Naming Trap: CamelCase & 'on' Prefixes in CustomEvent Constructors
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}).
Core Rule: Use { bubbles: false, composed: false } for 95% of use cases to maintain component encapsulation, keep event names strictly lowercase without spaces or hyphens, and pass payloads inside event.detail.
  • Pass Serialized Primitives: Send IDs or clean payload objects inside detail rather 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.