Recently, I designed a to-do list app in Lightning Web Components (LWC). The main list component contains multiple task components, each of which is a child component, and the parent list manages them together.
At first, I considered using parent-to-child event communication to handle communication between components. However, I chose to use component composition instead because it provides better organization and makes managing child components within the parent easier.
In this Salesforce tutorial, we will learn about component composition in Salesforce Lightning Web Components using a real-time Todo list example.
What is Component Composition in Lightning Web Component?
In Salesforce Lightning Web Components (LWC), component composition means adding the functionality to a parent component by including one or more child components. But here, the child components are not directly nested inside the parent component. The parent component controls and uses the features of its child components by nesting them together inside a controller component.
In the Lighting Web Component composition, we have three elements: the Owner Component, the Container Component, and the Child Component.
So, first, let’s understand the details of these components.
Owner Component:
The owner is the component that owns the template. In this example, the owner is the c-todo-list component. The owner controls all the composed components that it contains. The owner component can:
- Set public properties on composed (child) components.
- Call methods on composed components.
- Listen for any events fired by the composed components.
Container Component:
The container contains other components, but itself is contained within the owner component. In this example, c-todo-wrapper is a container. A container is less powerful than the owner. A container can:
- Read, but do not change, public properties in contained components.
- Call methods on composed components.
- Listen for some, but not necessarily all, events called by the components that it contains.
- This container component acts as a bridge between the owner and the child.
Child Component:
The child component is the most nested component in the composition. It is contained within a container or directly within the owner. In this example, c-todo-item is the child component. The child component typically:
- Exposes public properties and methods to the parent or container component.
- Use custom events to communicate changes or user actions to the container or owner component.
- Manages its own internal state and UI rendering based on input from its parent.
Now, we will create the components in the order Child Component -> Container -> Owner Component.
The structure of the composition will be as shown below:

Create the Child Lightning Web Component
Navigate to the VS Code IDE, create the Lighting web component, and label it as todoItem.
After creating the lightning web component, enter the below code in the todoItem.js file.
import { LightningElement, api } from 'lwc';
export default class TodoItem extends LightningElement {
@api item;
handleDelete() {
this.dispatchEvent(new CustomEvent('deleteitem', {
detail: this.item.id
}));
}
}In the above code, the @api item receives a task object ({ id, name }) from the parent. We have defined a method handleDelete() to delete the item from the list. This method will send a custom event with the task ID to the parent to remove the item from the list.
After this, enter the code below in the todoItem.html file to define the UI of the child component.
<template>
<div class="slds-box slds-m-bottom_small">
<p>{item.name}</p>
<lightning-button
variant="destructive"
label="Delete"
onclick={handleDelete}>
</lightning-button>
</div>
</template>This will render a list that displays the task name ({item.name}) and a “Delete” button. When the button is clicked, it triggers the handleDelete method defined in the component’s JavaScript.
To expose the component, set <isexposed> = true in the meta.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>63.0</apiVersion>
<isExposed>true</isExposed>
</LightningComponentBundle>With this, we have completed the child component. Now, this component will be composed in the Container component in the further steps.
Create the Container Lightning Web Component
Now, we will create the container component, todoWrapper, that will receive todos from the owner (todoList) and render <c-todo-item> for each task.
Enter the code below in todoWrapper.js.
import { LightningElement, api } from 'lwc';
export default class TodoWrapper extends LightningElement {
@api todos = [];
handleDeleteItem(event) {
this.dispatchEvent(new CustomEvent('deleteitem', {
detail: event.detail
}));
}
}After this, enter the code below in todoWrapper.html.
<template>
<template if:true={todos}>
<template for:each={todos} for:item="todo">
<c-todo-item
key={todo.id}
item={todo}
ondeleteitem={handleDeleteItem}>
</c-todo-item>
</template>
</template>
</template>We created the container to receive the to-do list and render multiple child components.
Create the Owner Lightning Web Component
Now, we will create the Owner component to display the task input, manage the to-do list, and render each task using the child component.
Create the Lightning web component and name it as todoList, and enter the code below in the todoItem.js file.
import { LightningElement } from 'lwc';
export default class TodoList extends LightningElement {
newTask = '';
todos = [
{ id: 1, name: 'Buy groceries' },
{ id: 2, name: 'Finish project' }
];
handleInputChange(event) {
this.newTask = event.target.value;
}
addTask() {
if (this.newTask.trim()) {
const newItem = {
id: Date.now(),
name: this.newTask
};
this.todos = [...this.todos, newItem];
this.newTask = '';
}
}
handleDelete(event) {
const idToDelete = event.detail;
this.todos = this.todos.filter(todo => todo.id !== idToDelete);
}In the above code, the handleInputChange method gets the user’s input as they type in the text field. The addTask method creates a new task object and adds it to the to-do list when the user clicks the “Add Task” button.
The handleDelete method listens for the deleteitem event emitted by the child component and removes the corresponding task from the list based on its ID.
After this, enter the code below in the todoList.html.
<template>
<lightning-card title="To-Do List" icon-name="standard:task">
<div class="slds-p-horizontal_medium slds-p-vertical_small">
<lightning-input
label="New Task"
value={newTask}
onchange={handleInputChange}
class="slds-m-bottom_small">
</lightning-input>
<lightning-button
label="Add Task"
variant="brand"
onclick={addTask}>
</lightning-button>
<template if:true={todos}>
<template for:each={todos} for:item="todo">
<c-todo-item
key={todo.id}
item={todo}
ondeleteitem={handleDelete}>
</c-todo-item>
</template>
</template>
</div>
</lightning-card>
</template>The above component renders an input field and an “Add Task” button to allow users to enter and submit new tasks. It then iterates over the todos array and renders a <c-todo-item> component for each task in the list.
Additionally, it listens for the deleteitem event from each child component using the ondeleteitem={handleDelete} attribute, enabling the parent component to handle task deletion when triggered.
After this, make the Owner component available to the lighting pages by entering the code below in the meta.xml file.
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__RecordPage</target>
</targets>Now, deploy the Lightning Web Component to the Lightning page. For this, navigate to the Lightning page and click on the settings icon, then select Edit page.
In the Lightning App builder, select the LWC component todoList for the page region and save the changes.

Navigate back to the lightning page, and there you will see the TodoList component (owner) that we have deployed.

This component allows users to enter new tasks, view the list of existing tasks, and delete any task using the delete button. Each task is displayed using the todoItem child component, showcasing how component composition and event handling work in Salesforce LWC.
Component Composition in Salesforce LWC Using Apex
In this component composition example, the Owner component will fetch the data from an Apex controller, retrieving a list of contacts from Salesforce.
Once the data is fetched, it will be passed to the Container component, which iterates over each contact record.
For each contact, the container passes the data to the child component, which shows the contact’s details like name, email, and phone.
Create the Apex Controller Class
In this example, we will fetch the data in Owner LWC using Apex. First, we will create the Apex controller class to fetch the contact data.
public with sharing class ContactController {
@AuraEnabled(cacheable=true)
public static List<Contact> getContacts() {
return [SELECT Id, Name, Email, Phone FROM Contact LIMIT 10];
}
}In this controller class, the method getContacts is marked with @AuraEnabled(cacheable=true), so it can be called from an LWC using the @wire service.
Create the Child Lightning Web Component
Now, we will create a child component that displays a single contact’s name, email, and phone. It uses the @api decorator to receive data from the parent (container).
For the child component, I have created a Lightning Web Component contactCard. Enter the code below in the contactCard.js file.
import { LightningElement, api } from 'lwc';
export default class ContactCard extends LightningElement {
@api contact;
}After this, enter the code below in the HTML file.
<template>
<lightning-card title={contact.Name} icon-name="standard:contact">
<div class="slds-p-horizontal_medium">
<p><strong>Email:</strong> {contact.Email}</p>
<p><strong>Phone:</strong> {contact.Phone}</p>
</div>
</lightning-card>
</template>This component will receive a single contact record as input using @api contact. Then, it will display the contact’s name, email, and phone in Lightning Card UI.
Create the Container Lightning Web Component
After the child component, we will create the Container component to handle the contact list layout. This component will receive the list of contacts and loop over them. For each contact, it renders a contactCard (child).
Create the Lightning Web Component contactWrapper and enter the below code in contactWrapper.js file.
import { LightningElement, api } from 'lwc';
export default class ContactWrapper extends LightningElement {
@api contacts = [];
}After this, enter the code below in the contactWrapper.html file.
<template>
<template if:true={contacts}>
<template for:each={contacts} for:item="con">
<c-contact-card key={con.Id} contact={con}></c-contact-card>
</template>
</template>
</template>It will loop through each contact using for:each and pass each one to the contactCard child component.
Create the Owner Lightning Web Component
Now, we will create the Owner component to complete the component composition. This component will fetch contact data from the Apex method using the @wire service and send it to the container (contactWrapper).
Create the Lightning Component contaclList and enter the code below in the contactList.js file.
import { LightningElement, wire } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';
export default class ContactList extends LightningElement {
contacts;
@wire(getContacts)
wiredContacts({ error, data }) {
if (data) {
this.contacts = data;
} else {
console.error('Error fetching contacts:', error);
}
}
}After this, enter the code below in the contactList.html file.
<template>
<lightning-card title="Contact List (Owner)" icon-name="standard:contact_list">
<div class="slds-p-around_medium">
<template if:true={contacts}>
<c-contact-wrapper contacts={contacts}></c-contact-wrapper>
</template>
<template if:false={contacts}>
<p>Loading...</p>
</template>
</div>
</lightning-card>
</template>The cobe code displays a card titled “Contact List (Owner)” and, once contacts are loaded, it passes them to the contact-wrapper component; otherwise, it shows a “Loading…” message.
At last, make the Owner component exposed to the lightning pages using the code below in the meta.xml file.
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__RecordPage</target>
</targets>Deploy the Lightning Web Component contact list to the lightning page, and there you will see the list of contact records with their email and phone.

This way, we can compose components in Salesforce Lightning Web Components using Apex to fetch the data in the components.
I hope that you understand how component composition works in Salesforce Lightning Web Components and how we can use it. With this, you can inherit multiple child components in a container component. Then, call the child components in the Owner (parent) through the container to use their functionality.
You may also like to read:
- Create Image Cropper in Salesforce Lightning Web Component
- Create a Custom Spinner In Salesforce Lightning Web Components
- Use Lightning Message Service (LMS) in Salesforce LWC
- Call Apex Method With Parameters in Lightning Web Component
I am Bijay Kumar, the founder of SalesforceFAQs.com. Having over 10 years of experience working in salesforce technologies for clients across the world (Canada, Australia, United States, United Kingdom, New Zealand, etc.). I am a certified salesforce administrator and expert with experience in developing salesforce applications and projects. My goal is to make it easy for people to learn and use salesforce technologies by providing simple and easy-to-understand solutions. Check out the complete profile on About us.