Skip to main content

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

CriteriaLightningModalWrapper Component
Accessibility (focus trap, aria)Built-inManual implementation
Quick Action supportDirectRequires Aura wrapper
Data return to parentthis.close(data)Custom events
Layout controlConstrained to modal regionsFull DOM control
Size optionssmall, medium, large, fullAny CSS sizing
Minimum API version55.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.