Easily Add Confirmation Dialogs to your Lightning Components with lwcModal!

Example of a “confirmation interaction”, invoked programmatically from an event handler

Overview

lwcModal provides an easy way for LWC developers to add modals (aka pop-up dialogs) to components they’re building. It serves two related but distinct use cases:

  • Customizable, composable modal. Pass any markup into the modal, including custom buttons. This part I shamelessly stole took inspiration from James Simone’s blog post and component and made a few tweaks.
  • Programmatic user confirmation. Embed user confirmation—that is, presenting the user with a message and the choice to confirm an action or cancel it—into your functions without modifying your markup. This is what really makes lwcModal worth talking about, IMO.

Features

Custom Modal

I highly recommend reading James’ blog post because mostly I’m just trying to replicate what he did. You can wrap lwcModal around any custom markup you define. The modal is hidden by default. Select the component using this.template.querySelector(), then call the component’s open() function to display the modal and close() to hide it again. This is commonly used for create/edit screens.

You can optionally set any of the following properties:

NameRequiredDefaultDescription
headerHeader text of the modal. Default is none.
cancelButtonLabelCancelLabel for the Cancel button.
confirmButtonLabelLabel for the Confirm button. Unless a value is provided, the Confirm button is not displayed.
confirmButtonVariantbrandVariant for the Confirm button. Supported values are brand (default), neutral, outline-brand, destructive, text-destructive, success.
validateOnConfirmfalseBefore dispatching the Confirm event, run reportValidity() on all Lightning input elements that have been slotted in and only proceed if they all pass.

The component dispatches the following custom events:

  • buttonclick: fires when the user clicks any of the footer buttons (Confirm, Cancel, or a custom slotted button).
  • confirm: fires when the user clicks the Confirm button.
  • cancel: fires when the user clicks the Cancel button.

Note that the buttonclick event is dispatched any time any of the buttons in the modal footer are clicked. When the Confirm or Cancel button are clicked, a second event is then also dispatched: confirm or cancel, respectively. As a developer you can mostly just use onconfirmonbuttonclick is primarily designed for the confirmation functionality and oncancel is rarely necessary (normally just having the modal close when the user clicks Cancel is all you want, and it does that on its own).

Programmatic Confirmation

A common use case for confirmation is when the user clicks a button that triggers an important action, such as deleting a record or submitting a form, to protect against the possibility of an accidental click or warn the user of the impact their action will have. Currently, there’s no native LWC way to capture this confirmation. Developers can use modals (either building their own or installing a custom component) to pop up the confirmation, but each individual confirmation requires modifying your markup and adding complexity to your code. lwcModal allows you to add just one compact, generic “confirmation” component to your markup and programmatically invoke it from one or more of the event handlers in your component.

Below is a simplified example of programmatically requiring user confirmation for a given action.

lwcModalDemo.html

<template>
<lightning-button
label="Delete EVERYTHING"
icon-name="utility:delete"
onclick={handleDeleteClick}></lightning-button>

<c-lwc-modal
class="confirmation"
confirmation={confirmation}
onbuttonclick={handleModalButtonClick}></c-lwc-modal>
</template>

lwcModalDemo.js

import { LightningElement } from 'lwc';
import { getConfirmation, handleConfirmationButtonClick } from 'c/lwcModalUtil';

export default class LwcModal extends LightningElement {
    @track confirmation;
    
    deleteConfirmationDetails = {
        text: 'Are you sure you want to delete everything?',
        confirmButtonLabel: 'Delete',
        confirmButtonVariant: 'destructive',
        cancelButtonLabel: 'Never mind',
        header: 'Confirm Delete'
    };
            
    handleDeleteClick(event) {
        this.confirmation = getConfirmation(
            this.deleteConfirmationDetails, // modal configurations
            () => this.deleteEverything(), // callback function when user 'confirm' clicks confirm
            // optional: () => this.handleCancel()
        );
    }
    
    // We pass the event to the function imported from the utility class along with the confirmation object
    handleModalButtonClick(event) {
        handleConfirmationButtonClick(event, this.confirmation);
    }
    
    deleteEverything() {
        // Delete everything... or whatever you want to happen when the user confirms
    }
}

Our markup contains a standard issue Delete button with a standard issue onclick event handler. Next is our lwcModal component, which is hidden from view until it’s called upon by the controller. The modal has an event handler for a custom event called onbuttonclick, which calls handleModalButtonClick. The other important attribute is confirmation, which represents the confirmation interaction, as we’ll see in the JS controller.

What our simple lwcModalDemo component looks like when rendered
The confirmation dialog that pops up when a user clicks the Delete button

In our JS controller, we first import two functions from lwcModalUtil

  • getConfirmation() will be used to generate a confirmation instance
  • handleConfirmationButtonClick() is invoked when the user clicks either the confirm or cancel button in the modal

Then we declare our confirmation object. Again, this object represents our confirmation interaction, by which I mean the process of presenting a message to the user, asking them to click either a Confirm or a Cancel button, and then acting on the user’s selection. As such, our confirmation object consists of the following properties:

  • details: (required) defines the look and feel of a modal, such as header and body text and button labels.
  • onconfirm(): (required) a callback function to be invoked when the user clicks the Confirm button.
  • oncancel(): (optional) a callback function to be invoked when the user clicks the Cancel button.

Next we define what we want our Delete confirmation modal to look like with deleteConfirmationDetails. This details object supports the following properties:

NameRequiredDefaultDescription
textTRUEBody text of the modal. This should explain to the user what you are asking them to confirm or cancel.
headerHeader text of the modal.
confirmButtonLabelConfirmLabel for the “confirm” button.
confirmButtonVariantbrandVariant for the “confirm” button. Supported values are brand (default), neutral, outline-brand, destructive, text-destructive, success.
cancelButtonLabelCancelLabel for the “cancel” button.

Now it’s time to bring it all together. In handleDeleteClick(), which is called anytime our demo Delete button is clicked, we set the value of our confirmation object by calling getConfirmation, which we imported from the utility class. It takes the following parameters:

NameRequiredDescription
detailsTRUEThe configuration details for a confirmation modal. This value can either be a details object (see above), or a string value containing the modal body text; if you enter a string, all other details properties will use their default values.
onconfirmTRUECallback function to be executed when the user clicks the confirm button.
oncancelCallback function to be executed when the user clicks the cancel button. By default, the modal closes and no other actions are taken.

Last but not least, we have handleModalButtonClick, which is the event handler for the onbuttonclick event on the lwcModal component. That means that this handler gets invoked when the user clicks the Confirm or Cancel buttons. All we need to do here is call the handleConfirmationButtonClick function from the utility file and pass along event and our configuration object as we’ve defined it. Since we already defined which actions the modal is supposed to take onconfirm and oncancel (if any), this function simply tells the component which of those outcomes to trigger.

Source Code

https://github.com/fromsteinsfdc/lwc-modal

Note that the repo also contains a component lwcModalTest which serves as a more sophisticated live demo than our sample code above, and showcases 3 different use cases of lwcModal. Once installed, it can be dropped onto any Lightning App or Home page.

Screenshots & Examples

In addition to the confirmation functionality, whose utility is hopefully clear by now, another common use for modals is editing data. This is used not only for custom components, but is a common interface throughout Salesforce, like when you click “Edit” on a record page:

Example of using a modal for editing details within Salesforce’s standard UI

A recent example was the “Button Builder” interface I built for the Flow Button Bar component’s Custom Property Editor. I needed a way to enable users to modify multiple properties for each button without taking up a ton of space in the main editor. The solution? Show just the buttons themselves in the main editor, but allow users to open an “edit” modal for any one button. Here’s a quick clip of what that looks like:

An example of using a modal as a create/edit screen
An example of using a modal as a create/edit screen

Here’s another, simpler example of an input modal:

They can also be useful for simply presenting information to the user, either to let them know what’s about to happen before some kind of transition, or as a form of advanced help-text, presenting supplemental information to the user, as in the following examples:

Example of using a modal to display additional information to the user
Example of using a modal to display additional information to the user. In this example, clicking a column header launches a modal containing more detail about that field’s purpose.
Example of using a modal to display data that is optional, supplementary, or simply doesn’t fit within the main component