LWC Modal Patterns: LightningModal vs. Wrapper Components
Modals are a fundamental UI pattern in Salesforce Lightning applications. LWC provides two primary approaches, each with distinct trade-offs.
Approach 1: Extending LightningModal
The LightningModal base class (available since Spring '23) is the recommended approach for most modal use cases. It handles backdrop rendering, focus trapping, and accessibility out of the box.
Component Definition
// confirmationModal.js
import LightningModal from "lightning/modal";
import { api } from "lwc";
export default class ConfirmationModal extends LightningModal {
@api title;
@api message;
@api items;
handleConfirm() {
this.close({ confirmed: true, selectedItems: this.items });
}
handleCancel() {
this.close({ confirmed: false });
}
}
<!-- confirmationModal.html -->
<template>
<lightning-modal-header label={title}></lightning-modal-header>
<lightning-modal-body>
<p>{message}</p>
<template if:true={items}>
<ul class="slds-list_dotted slds-m-top_small">
<template for:each={items} for:item="item">
<li key={item.Id}>{item.Name}</li>
</template>
</ul>
</template>
</lightning-modal-body>
<lightning-modal-footer>
<lightning-button label="Cancel" onclick={handleCancel}></lightning-button>
<lightning-button
label="Confirm"
variant="brand"
onclick={handleConfirm}
></lightning-button>
</lightning-modal-footer>
</template>
Opening the Modal and Handling Results
// parentComponent.js
import ConfirmationModal from "c/confirmationModal";
async handleDeleteClick() {
const result = await ConfirmationModal.open({
size: "small", // 'small', 'medium', 'large', 'full'
title: "Confirm Deletion",
message: "Are you sure you want to delete these items?",
items: this.selectedItems,
});
if (result?.confirmed) {
await this.deleteItems(result.selectedItems);
}
}
The .open() method returns a Promise that resolves with whatever value is passed to this.close(). This makes data round-tripping between parent and modal clean and predictable.
Quick Action Support
LightningModal components can serve as Quick Action targets directly. Set the action type to a Lightning Web Component and reference the modal component. No Aura wrapper is needed.
Approach 2: Wrapper Component Pattern
When LightningModal limitations apply -- for example, needing granular control over the modal's DOM, embedding a component that requires full-width layout, or supporting older org configurations -- a wrapper component approach is the alternative.
The LWC Content Component
<!-- editPanel.html -->
<template>
<section
role="dialog"
tabindex="-1"
aria-modal="true"
class="slds-modal slds-fade-in-open slds-modal_large"
>
<div class="slds-modal__container">
<header class="slds-modal__header">
<h2 class="slds-modal__title">{headerTitle}</h2>
</header>
<div class="slds-modal__content slds-p-around_medium">
<!-- Full control over content layout -->
<slot></slot>
</div>
<footer class="slds-modal__footer">
<lightning-button label="Close" onclick={handleClose}></lightning-button>
</footer>
</div>
</section>
<div class="slds-backdrop slds-backdrop_open"></div>
</template>
// editPanel.js
import { LightningElement, api } from "lwc";
export default class EditPanel extends LightningElement {
@api headerTitle = "Edit";
handleClose() {
this.dispatchEvent(new CustomEvent("close"));
}
}
Aura Wrapper for Quick Actions
If a wrapper-style modal needs to launch from a Quick Action, an Aura component is required as the intermediary:
<!-- editPanelAction.cmp -->
<aura:component implements="force:lightningQuickAction,force:hasRecordId">
<c:editPanel
record-id="{!v.recordId}"
onclose="{!c.handleClose}"
></c:editPanel>
</aura:component>
// editPanelActionController.js
({
handleClose: function (component, event) {
$A.get("e.force:closeQuickAction").fire();
$A.get("e.force:refreshView").fire();
},
});
When to Use Each Approach
| Criteria | LightningModal | Wrapper Component |
|---|---|---|
| Accessibility (focus trap, aria) | Built-in | Manual implementation |
| Quick Action support | Direct | Requires Aura wrapper |
| Data return to parent | this.close(data) | Custom events |
| Layout control | Constrained to modal regions | Full DOM control |
| Size options | small, medium, large, full | Any CSS sizing |
| Minimum API version | 55.0 (Spring '23) | Any |
Event Cleanup
When a modal contains components with document-level event listeners, always clean them up when the modal closes. For LightningModal, use disconnectedCallback. For wrapper components, handle cleanup in the close event handler before removing the modal from the DOM.
CSS Considerations
Wrapper modals require explicit SLDS classes (slds-modal, slds-backdrop, slds-fade-in-open) and proper z-index stacking. LightningModal handles all of this automatically. If you need scrolling content in a wrapper modal, apply overflow-y: auto and a max-height to the slds-modal__content container.
For most new development, LightningModal is the better choice. Reserve the wrapper pattern for cases where you need capabilities that LightningModal does not expose.