Modal

lightning/modal

Extend this component to create a modal window overlayed on the current app window.

For Use In

Lightning Experience, Standalone Lightning App

The lightning/modal module provides the LightningModal component to create a modal window on top of the current app window. A modal interrupts a user’s workflow.

LightningModal implements the SLDS modals blueprint.

Create a modal component in response to a user action, such as clicking a button or link. The modal blocks interaction with everything else on the page until the user acts upon or dismisses the modal.

Unlike other components, this component doesn't use a lightning-modal tag or extend LightningElement. There is no lightning-modal component. Instead, you create a modal by extending LightningModal and using these helper lightning-modal-* components to provide a header, footer, and the body of the modal.

  • lightning-modal-body
  • lightning-modal-header
  • lightning-modal-footer

To create a modal component, import LightningModal from lightning/modal. The component has access to the normal LWC resources as well as the special container, helper components, methods, and events of the lightning/modal module.

In this example, the content property passes data to the modal from the component that invokes it.

1/* c/myModal.js */
2
3import { api } from "lwc";
4import LightningModal from "lightning/modal";
5
6export default class MyModal extends LightningModal {
7  @api content;
8
9  handleOkay() {
10    this.close("okay");
11  }
12}

The modal’s HTML template uses helper lightning-modal-* components to provide a header, footer, and the body of the modal. In this example, the content for the modal body comes from the content property we defined in the modal's JavaScript file.

1<!-- c/myModal.html -->
2
3<template>
4    <lightning-modal-header label="My Modal Heading"></lightning-modal-header>
5    <lightning-modal-body> Content: {content} </lightning-modal-body>
6    <lightning-modal-footer>
7        <lightning-button label="OK" onclick={handleOkay}></lightning-button>
8    </lightning-modal-footer>
9</template>

The lightning-modal-body component is required for the modal template.

The lightning-modal-header and lightning-modal-footer components are optional, but recommended. The lightning-modal-* components render in the order they appear in the template. Place the lightning-modal-body component after lightning-modal-header and before the lightning-modal-footer component.

Open a Modal Instance 

LightningModal provides an .open() method, which opens a modal and returns a promise that asynchronously resolves with the result of the user’s interaction with the modal.

Each invocation of a modal component’s .open() method creates a unique instance of the modal. You can think of a modal as a self-contained application that starts from scratch when it opens. It displays the content that you pass in through the .open() method or that you set within the modal's HTML template.

When you close a modal, the modal instance is destroyed, not hidden. On close, the modal must save the user’s input data or pass it to the invoking component as the promise result. Otherwise, the data is lost when the modal instance is closed.

The .open() method lets you assign values to the modal's properties. LightningModal provides these properties.

  • label - Required. Sets the modal's title and assistive device label. If the modal has a header, set label in the lightning-modal-header component. If the modal doesn't have a header, set the label property when opening the modal.

  • size - Determines how much of the viewport width the modal uses. Supported values are small, medium, large, and full. You can set the size attribute when you open the modal. You can't change the modal size after the modal is opened. Default value is medium. The height of the modal is determined by the amount of content in the lightning-modal-body component, and the size value. For more information on the full variant, see the Styling Variants section.

  • description - Sets the modal's accessible description. It uses the aria-description attribute where supported, or falls back to aria-describedby. If you set a description value, include the label name at the beginning of your description text. Some screen readers only read the description, and not the label.

  • disableClose - Prevents closing the modal by normal means like the ESC key, the close button, or .close(). For example, you can briefly set disableClose to true while a completed form is saving, so the user doesn't dismiss the modal early. See Use the disableClose Attribute.

In this example, the app opens the myModal component. It invokes the .open() method in a handleClick() function bound to the app button’s onclick attribute, and uses async and await keywords to handle the promise returned by .open(). This example overrides the size value by setting it to large and sets the modal’s user-defined property content.

1/* c/myApp.js */
2
3import { LightningElement } from "lwc";
4import MyModal from "c/myModal";
5
6export default class MyApp extends LightningElement {
7  async handleClick() {
8    const result = await MyModal.open({
9      // `label` is not included here in this example.
10      // it is set on lightning-modal-header instead
11      size: "large",
12      description: "Accessible description of modal's purpose",
13      content: "Passed into content api",
14    });
15    // if modal closed with X button, promise returns result = 'undefined'
16    // if modal closed with OK button, promise returns result = 'okay'
17    console.log(result);
18  }
19}

The HTML template for this app contains a button that opens the modal and displays the result returned when the modal closes.

1<!-- c/myApp.html -->
2
3<template>
4    <lightning-button
5        onclick={handleClick}
6        aria-haspopup="dialog"
7        label="Open My Modal">
8    </lightning-button>
9    <p>Result: {result}</p>
10</template>

You can also use .open() to pass data from your invoking component into the modal with custom properties decorated with @api. These properties can be any type, such as a string or an object that’s an array of key/value pairs to be assigned to the new modal instance.

For example, this app component sets an options property to a set of key/value pairs in MyModal.open(). The promise is handled using an arrow function that logs the result to the console.

1/* c/myApp.js */
2
3import { LightningElement } from "lwc";
4import MyModal from "c/myModal";
5
6export default class MyPage extends LightningElement {
7  handleOpenClick() {
8    MyModal.open({
9      // maps to developer-created `@api options`
10      options: [
11        { id: 1, label: "Option 1" },
12        { id: 2, label: "Option 2" },
13      ],
14    }).then((result) => {
15      console.log(result);
16    });
17  }
18}

The modal dialog can then use this property. In this case, the modal creates buttons whose labels are provided in the options array.

1/* c/myModal.js */
2
3import { api } from "lwc";
4import LightningModal from "lightning/modal";
5
6export default class MyModal extends LightningModal {
7  // Data is passed to api properties via .open({ options: [] })
8  @api options = [];
9}
1<!-- c/myModal.html -->
2
3<template>
4    <lightning-modal-header label="My Modal Heading"></lightning-modal-header>
5    <lightning-modal-body>
6        Let's make some buttons!
7        <template for:each={options} for:item="option">
8            <lightning-button
9              onclick={handleOptionClick}
10              data-id={option.id}
11              key={option.id}
12            >
13                {option.label}
14            </lightning-button>
15        </template>
16    </lightning-modal-body>
17</template>

Close a Modal Instance 

Use this.close(result) to close the modal, where result is anything you want to return from the modal. The .close() operation is asynchronous to display a brief fade out animation before the modal is destroyed. The result data can’t be modified after the close operation begins.

You can also close the modal with the default close button, the X in the top right corner. Closing a modal like this is the same as calling this.close() with an undefined result, so any data input is lost.

This example shows the handler for some buttons inside a modal. When the user clicks the button, this.close(id) returns the option that they chose and the modal closes.

1/* c/myModal.js */
2
3import { api } from "lwc";
4import LightningModal from "lightning/modal";
5
6export default class MyModal extends LightningModal {
7  // Data is passed to apis via .open({ options: [] })
8  @api options = [];
9
10  handleOptionClick(e) {
11    const { target } = e;
12    const { id } = target.dataset;
13    // this.close() triggers closing the modal
14    // the value of `id` is passed as the result
15    this.close(id);
16  }
17}
1<!-- c/myModal.html -->
2
3<template>
4  <template for:each={options} for:item="option">
5    <lightning-button
6      onclick={handleOptionClick}
7      data-id={option.id}
8      key={option.id}
9    >
10      {option.label}
11    </lightning-button>
12  </template>
13</template>

Use the disableClose Attribute 

disableClose temporarily turns off the ability to dismiss the modal by normal means like the ESC key, the close button, or this.close(). For example, you could briefly set disableClose to true while a completed form is saving, so the user doesn't dismiss the modal early.

Preventing a modal's close should be a temporary state. Use disableClose for less than 5 seconds. Accessibility is affected if you trap keyboard focus inappropriately. See No Keyboard Trap for more information.

When the process using disableClose finishes successfully, dismiss the modal. If the process using disableClose fails, re-enable the ability to dismiss the modal with disableClose = false, or give feedback to help the user resolve the issue.

While disableClose is true, disable any processes or UI buttons that might call Modal.close().

This example shows how to use disableClose. It's not a fully functional example, but could be used in a complex modal component.

1/* c/myModalForm.js */
2/* This is intended as a mock example, not as fully functional code */
3/* NOTE: observe values set:
4   this.disableClose (in JS),
5   and this.saveInProcess (in JS and HTML)
6*/
7import { api } from "lwc";
8import LightningModal from "lightning/modal";
9
10export default class MyModalForm extends LightningModal {
11  // formData is utilized for saving current form values
12  formData = {};
13  failureType = null;
14  saveStatus = {};
15  saveInProcess = false;
16
17  handleCloseClick() {
18    this.close("canceled");
19  }
20
21  closeModal() {
22    // immediately exits, so no need to trigger
23    // this.disableClose = false OR
24    // this.saveInProcess = false;
25    // modal is destroyed, and focus moves
26    // back to originally clicked item that
27    // generated the modal
28    this.close("success");
29  }
30
31  mitigateSaveFailure() {
32    // depending on how easily the failure can be resolved
33    // you may need to immediately set disableClose = false
34    if (this.failureType === "recoverable") {
35      // no need to call this.disableClose = false
36      // or this.saveInProgress = false yet
37      tryToFixFailure();
38    } else {
39      // can't resolve the error
40      // need to allow users to exit modal
41      this.disableClose = false;
42      this.saveInProcess = false;
43      // mock function to indicate modal state
44      // while still allowing user to exit
45      // preventing keyboard trap
46      reportUnresolvableError();
47    }
48  }
49
50  async saveData() {
51    // switches disabled state on buttons
52    this.saveInProcess = true;
53    const saveStatus = await sendData(this.formData);
54    return saveStatus && saveStatus.success ? closeModal() : mitigateSaveFailure();
55  }
56
57  async handleSaveClick() {
58    if (isValid(this.formData)) {
59      // begin saving data, temporarily disable
60      // LightningModal's close button
61      // Be sure to reenable the close button, by setting
62      // this.disableClose = false, IF further interaction
63      // is desired before the modal closes
64      this.disableClose = true;
65      await saveData();
66    } else {
67      // function that display form errors based on data
68      showFormErrors(this.formData);
69    }
70  }
71}
1<!-- c/myModalForm.html -->
2
3<template>
4    <lightning-modal-header
5      label="My Modal Heading"
6    ></lightning-modal-header>
7    <lightning-modal-body>
8      <!-- form here -->
9    </lightning-modal-body>
10    <lightning-modal-footer>
11      <button
12        disabled={saveInProcess}
13        onclick={handleCloseClick}
14        aria-label="Cancel and close"
15      >Cancel</button>
16      <button
17        disabled={saveInProcess}
18        onclick={handleSaveClick}
19      >Save</button>
20    </lightning-modal-footer>
21</template>

Modal Events 

A modal can only fire events captured by the component that opened it, not the modal itself. Normal techniques can't catch events that fire from the modal, because the events bubble up to a top root element outside the component that opened the modal.

To capture modal events, attach them in the .open() method invoked by the component that opens the modal.

Capturing modal events requires Lightning Web Security (LWS) to be enabled in the Salesforce org. See the Modal Events with LWS and Lightning Locker section for more information.

For example, here's a custom select event dispatched from MyModal.

1/* c/myModal.js */
2dispatchSelectEvent(e) {
3  // e.target might represent an input with an id and value
4  const { id, value } = e.target;
5  const selectEvent = new CustomEvent('select', {
6    detail: { id, value }
7  });
8  this.dispatchEvent(selectEvent);
9}

The open() method in myApp defines the onselect event handler. This example uses onselect to proxy events that we want to pass through.

1/* c/myApp.js */
2
3// ...
4// Process the select event from within the modal
5handleSelectEvent(detail) {
6  const { id, value } = detail;
7  console.log(`select event fired elem with id ${id} and value: ${value}`);
8}
9
10// ...
11// Trigger visibility of the modal
12handleOpenModal() {
13  MyModal.open({
14    label: 'Modal Title',
15    size: 'large',
16    description: 'Modal Title with brief description',
17    // event triggered when new CustomEvent('select', {detail: {}});
18    // occurs *from within* LightningModal.
19    // see dispatchSelectEvent() in c/myModal.js above
20    onselect: (e) => {
21      // stop further propagation of the event
22      e.stopPropagation();
23      // hand off to separate function to process
24      // result of the event (see above in this example)
25      this.handleSelectEvent(e.detail);
26      // or proxy to be handled above by dispatching
27      // another custom event to pass on the event
28      // this.dispatchEvent(e);
29    }
30  });
31}

See Create and Dispatch Events in the LWC Dev Guide for more information about events.

Modal Events with LWS and Lightning Locker 

Modal events work as expected when Lightning Web Security (LWS) is enabled within a Salesforce org, as described in the Modal Events section. If LWS isn't enabled in an org, Lightning Locker is in effect.

LWS is replacing Lightning Locker over time and is already enabled in many customer orgs. New orgs have LWS enabled by default. To enable LWS, see Enable Lightning Web Security in an Org in the Security for Lightning Components guide.

Under Lightning Locker, when you fire events within LightningModal, the browser throws a TypeError related to dispatchEvent. If your modal component runs in an org that can’t enable LWS yet, the workaround is to wrap the code that calls dispatchEvent in a child component that extends LightningElement. Use the wrapper component as a child of one of the modal components in the modal template.

See Navigate From a Modal in the LWC Dev Guide for an example.

Modal Events with Aura 

For LightningModal, only the top level LWC component or application can communicate to the parent Aura component or application layer with eventing. This topmost component is usually the one that opens the LightningModal. LightningModal's child components can't event to a parent Aura component or application layer.

All required eventing that should occur during the LightningModal's life cycle must be passed in when you call Modal.open(). See onselect within .open() in the Modal Events section.

If you want the Aura layer to respond to events within child components embedded in the LightningModal, use event bubbling to move any data that you want to make available to the Aura layer into the topmost LWC component that opened the modal. Then, send the event from the LWC component.

With this in mind, there are two suggested methods for extracting data from the LightningModal to the Aura layer.

  1. To only communicate data out of modal after it's closed, first, close the modal, and pass the data out with this.close({ data }), and handle the data received in .then((result) { ... }) where the Modal is initially opened.
  2. To continuously communicate data out of the LightningModal while the modal remains open, use events created and passed in when opening the modal from Modal.open({ size, title, onmyevent }).

These extracting methods fit into the larger LWC Modal-to-Aura event workflow.

  1. Pass any events that should occur within the modal through LightningModal's .open() method
  2. Have the LightningModal JavaScript code fire any custom events
  3. In the topmost LWC component that opened the modal, create an event handler to process the events, including stopping propagation.
  4. Fire a separate event containing the LWC-processed event details and send it to the Aura parent component.
  5. Use an Aura-based event handler to handle and process the event.

For more information, see Send Events to an Enclosing Aura Component and Events Best Practices.

Let's see this workflow in action. In this example, we'll create a button (lightning-button) that launches a modal (LightningModal) containing a tree grid component (lightning-tree-grid) with a button in each record row that automatically navigates our user to that record's page (lightning-navigation). This use case requires data passing between our LWC components and a parent Aura component.

Because lightning-navigation requires you to pass in a recordId to construct a page reference, we need to pass the record's data out of LightningModal, and then use the methods provided by lightning-navigation. With modal's limitations for event bubbling to the Aura layer, we can't just wrap the button or treegrid with lightning-navigation. We need to pull that data up to the topmost LWC component, where the LightningModal .open() is called, for our Aura wrapper to handle the navigation.

However, in Aura-based Experience Builder sites, the NavigationMixin event can reach the Aura layer without being affected by the modal's limitations.

So, we import the lightning-navigation component into the topmost LWC component that sits within the Aura layer by passing the data through LightningModal's close method, this.close({ data }). Then the parent LWC component invokes the methods provided by lightning-navigation, which handles the navigation away from the page after the modal exits. From a UX perspective, this also lets us close the modal first, since lightning-navigation takes the user away from the current page.

Note that this sample code isn't fully functional. We're only showing the relevant code that makes the lightning-navigation use case work with LightningModal.

Here's our Aura component, containing our topmost LWC component, myLwcAppModalLauncher.

1/* c/myApp.cmp */
2<aura:component>
3    <!-- <c:myLwcTreeGridWithNavigation/> component is a child component
4        inside of <c:myLwcAppModalLauncher/>
5    -->
6    <c:myLwcAppModalLauncher/>
7</aura:component>

The topmost component, myLwcAppModalLauncher, launches and handles events that occur within our modal (MyModal). myLwcAppModalLauncher is also where we import the lightning-navigation component and use the rowId handed up through modal's this.close({ ..., rowId }) function.

1<!-- c/myLwcAppModalLauncher.html -->
2
3<template>
4    <lightning-button
5        onclick={handleModalOpen}
6        aria-haspopup="dialog"
7        label="Launch Navigation Modal">
8    </lightning-button>
9</template>
1/* c/myLwcAppModalLauncher.js */
2
3import { LightningElement, wire } from 'lwc';
4import { NavigationMixin } from 'lightning/navigation';
5import MyModal from 'c/myModalWithTreeGridNavigation';
6
7export default class MyLwcAppModalLauncher extends NavigationMixin(LightningElement) {
8
9/// ... other modal code
10
11handleModalOpen() {
12    // open up the Modal
13    MyModal.open({
14        size: 'medium',
15        heading: 'Navigate to Record Page',
16        description: 'Navigate to a record page by clicking the row button',
17        options: [{ data }, { data }],
18    }).then((result) => {
19        // when the LightningModal closes, result is whatever
20        // data that was passed out using this.close({ data })
21        if (result === null) {
22            // do something else
23        } else {
24            // shouldNavigate is boolean, arbitrary
25            // rowId is what's needed for lightning-navigation
26            const { shouldNavigate, rowId } = result;
27            if (shouldNavigate) {
28                this.navigateToObjectHome(rowId);
29            }
30        }
31    });
32}
33
34navigateToObjectHome(rowId) {
35    // construct the page ref
36    const pageRef = {
37        type: 'standard__recordPage',
38        attributes: {
39            recordId: rowId,
40            actionName: 'view'
41        }
42    }
43    // then call the .Navigate method with the pageRef
44    this[NavigationMixin.Navigate](pageRef);
45}
46
47/* ... other modal code */

This template is our modal, containing a tree grid with the navigation buttons, named c-my-lwc-tree-grid-with-navigation. The handleNavSelection function in this component processes the rowId data for the modal component to pass to its parent.

1<!-- c/myModalWithTreeGridNavigation.html -->
2<template>
3    <lightning-modal-header label="My Modal Heading">
4        The modal tagline.
5    </lightning-modal-header>
6    <lightning-modal-body>
7        <h2>Choose a record to navigate to:</h2>
8        <!-- custom event listener when button clicked inside tree grid -->
9        <div onnavselection={handleNavSelection}>
10            <c-my-lwc-tree-grid-with-navigation></c-my-lwc-tree-grid-with-navigation>
11        </div>
12    </lightning-modal-body>
13    <!-- no <lightning-modal-footer> in this example -->
14</template>
1/* c/myModalWithTreeGridNavigation.js */
2handleNavSelection(event) {
3    event.stopPropagation();
4    const { detail: { rowId } } = event;
5    if (rowId) {
6        // rowId value is important to lightning-navigation
7        // passing this data out through the LightningModal's
8        // .close() method, then handled in the .then((result) {})
9        // see code above: c/myLwcAppModalLauncher.js
10        this.close({ shouldNavigate: true, rowId });
11    }
12}
1/* c/myLwcTreeGridWithNavigation.js */
2
3dispatchNavSelectionEvent(rowId) {
4    // add rowId to event.detail
5    // this custom event listener exists in
6    // LightningModal body code, see code above:
7    // c/myModalWithTreeGridNavigation.html
8    const eventToFire = new CustomEvent('navselection', {
9        detail: { rowId },
10        bubbles: true,
11        cancelable: false
12    });
13    // dispatch custom event
14    // see event listener added to lightning-modal-body
15    this.dispatchEvent(eventToFire);
16}
17
18handleRowButtonClick(event) {
19    // simplified handler to get row.Id
20    // from the tree grid row button that was clicked
21    const row = event.detail.row;
22    this.dispatchNavSelectionEvent(row.Id);
23}

Styling Forms in Native Shadow 

NOTE:

  • Due to the intentional changes introduced with Native Shadow, it is important to understand that styles outside of your modal's code will not be able to style elements within your modal.
  • Don't import SLDS styles your web UI doesn't actually use. Adding extra CSS files will cause the performance of your web UI to be lowered

A common use case for LightningModal is to include a form and form elements with common SLDS layouts. While the individual Lightning Base Components will contain their own styles in Native Shadow, Lightning Design System form element styling for form layouts like the form element variants Stacked, Horizontal and Compound aren't present in Native Shadow, but still needed.

In order to address this, you can import the form element layout library, lightning/sldsFormElementUtils, into your LightningModal's implementation code, along with other commonly needed libaries.

Following the previous c/myModalWithTreeGridNavigation implementation example, you might import these styles in the following fashion.

1/* c/myModalWithTreeGridNavigation.css */
2
3/* provides base SLDS styling for H1-H6, P, UL, etc elements */
4/* recommended for most customers */
5@import "lightning/sldsCommon";
6/* provides SLDS form element styles for Stacked, Horizontal, Compound layouts */
7/* recommended for customers building forms, and need form element variant classes */
8@import "lightning/sldsFormElementUtils";
9/* provides column sizing for grid layouts */
10/* recommended for customers working with grid and grid sizing SLDS styles*/
11@import "lightning/sldsUtilsSizing";
12/* ... */
13.your-other-specific-styles-inside-the-modal {
14  border: 1px solid #c00;
15}

Styling Variants 

The lightning-modal-header and lightning-modal-footer child components are optional, and you can choose to not include one or the other in your modal.

The headerless variant of LightningModal has these additional requirements.

  • Must set a descriptive modal title with the label property using Modal.open({ label }) or you’ll get a console error.
  • The label property is required for all variants of LightningModal. Assistive devices read the label value, even though the headerless modal variant doesn't display the label.
  • Because this variant doesn't use lightning-modal-header, you have to manually create an <h1> heading in lightning-modal-body. Provide accessible structure by starting with heading level <h1> and using levels up to <h6> appropriately. For more information, see Semantic Structure, Headings on WebAim.org.

You can also create a full-screen modal component by setting the size attribute to full. This variant resizes the modal to the full width and height of the viewport on screens up to 30em (~480 pixels or less), like mobile phone devices. On screens larger than 30em (~481 pixels or larger), like desktop monitors or tablets, a size=full modal has the same behavior as a modal with size=large set.

The LightningModal component also supports the SLDS Directional variant modal blueprint pattern.

To achieve the directional button layout, place the buttons in a div with the slds-modal__footer_directional class.

1<!-- c/modalDirectional.html -->
2
3<template>
4    <lightning-modal-body> Content: {content} </lightning-modal-body>
5    <lightning-modal-footer>
6        <div class=“slds-modal__footer_directional”>
7            <lightning-button label="NO" onclick={handleNo}></lightning-button>
8            <lightning-button label="OK" onclick={handleOkay}></lightning-button>
9        </div>
10    </lightning-modal-footer>
11</template>

Styling Hooks 

The lightning-modal-* helper components support styling hooks. The styling hooks for the template that invokes the helper components doesn't carry over to them, so you must style each helper component individually.

Component styling hooks provide CSS custom properties that use the --slds-c-* prefix and they change styling for specific elements or properties of a component. Component styling hooks are supported for SLDS 1 only. See the SLDS 1 component blueprints for available component styling hooks.

For more information, see Style Components Using Lightning Design System Styling Hooks in the Lightning Web Components Developer Guide.

Accessibility 

Upon opening, the modal determines where to place focus as follows.

  • When the modal has multiple steps, with a different subtitle in the modal header for each step, the modal component places focus on the subtitle text.
  • When the modal doesn't have multiple steps and the modal header is present, the modal component places the focus on the title text in the header.
  • When a modal header isn’t present, the modal component places the focus on the first interactive element in the modal body, such as a link, button, or input field.
  • When the modal body doesn’t have interactive elements, or the only interactive element is a tooltip, the modal component places focus on the close button.

The focus is determined according to the Global Focus Guidelines for Lightning Design System.

The lightning-modal-header component renders the label value in an <h1> element. If your modal uses the header, begin any additional heading levels in the modal with <h2> for accessibility. Provide accessible structure by using heading levels up to <h6> appropriately. For more information, see Semantic Structure, Headings on WebAim.org.

To include tagline text or link content for the header section, add it between the <lightning-modal-header> tags.

Unit Testing 

Writing unit tests for LightningModal requires two sets of tests.

  1. Parent components that use c/myModal define a mocked result when .open() is called during a test run. This mock speeds up testing and prevents repetition while testing actions that trigger modals.
  2. The unit tests for c/myModal test the end-to-end functionality of the modal as a self-contained component.

This example is a mock of a component called myModal. The parent component c/myApp uses the modal to mock the c/myModal component's .open() method. The mock .open() resolves a constant value without opening the MyModal component. This example uses a button to open a modal and set the result in the template.

1<!-- c/myApp.html -->
2<button data-button onclick={handleClick}>Select Item</button>
3<div data-result>{result}</div>
1/* myApp.spec.js */
2
3import MyModal from 'c/myModal';
4jest.mock('c/myModal');
5
6test(() => {
7    // Create and appendChild(element)
8
9    const buttonEle = element.shadowRoot.querySelector('[data-button]');
10    const resultEle = element.shadowRoot.querySelector('[data-result]');
11
12    // Mock .open() to resolve result 'option1'
13    MyModal.open = jest.fn().mockResolvedValue('option1');
14    // Initial value
15    expect(resultEle.textContent).toBe(undefined);
16    // Click modal open button
17    buttonEle.click();
18
19    // Click handler render cycle
20    await Promise.resolve();
21    // Render cycle triggered by tracked value {result}
22    await Promise.resolve();
23
24    // Verify result is set in the prompt template
25    expect(resultEle.textContent).toBe('option1');
26    // Open triggered once
27    expect(MyModal.open.mock.calls).toHaveLength(1);
28})

This example contains the unit tests for the actual c/myModal component. The tests use shorthand selectors to make unit testing more concise. For more information, see Shorthand Selectors for LightningModal.

1/* myModal.spec.js */
2
3import { createElement } from "lwc";
4import MyModal from "c/myModal";
5
6describe("c-my-modal", () => {
7  afterEach(() => {
8    while (document.body.firstChild) {
9      document.body.removeChild(document.body.firstChild);
10    }
11  });
12
13  it("should select body elements", async () => {
14    const element = createElement("c-mymodal", {
15      is: MyModal,
16    });
17    document.body.appendChild(element);
18
19    // If element apis are modified after appendChild
20    // await Promise.resolve();
21
22    // using shorthand selector for modalBody
23    // see list of selectors below
24    expect(element.modalBody$("button").textContent).toBe("hello world");
25  });
26});

Shorthand Selectors for LightningModal 

The stubs for LightningModal have shorthand selectors to make unit testing more concise. These are all the shorthand selectors for unit testing that you can use with LightningModal.

  • element.closeValue - Value passed into this.close(value)
  • element.modalHeader$() - querySelect a single node in lightning-modal-header
    • Usage: element.modalHeader$('a') Returns first link
  • element.modalHeader$$() - querySelectAll multiple nodes in lightning-modal-header
    • Usage:
      • elem.modalHeader$$() Returns all nodes in header
      • elem.modalHeader$$('a') Returns all links in header
  • element.modalBody$() - querySelect a single node in lightning-modal-body
    • Usage: element.modalBody$('button') Returns first button
  • element.modalBody$$() - querySelectAll multiple nodes in lightning-modal-body
    • Usage:
      • elem.modalBody$$() Returns all nodes in body
      • elem.modalBody$$('button, [data-button]') Returns buttons in body
  • element.modalFooter$() - querySelect a single node in lightning-modal-footer
    • Usage: element.modalFooter$('button') Returns first button
  • element.modalFooter$$() - querySelectAll multiple nodes in lightning-modal-footer
    • Usage:
      • elem.modalFooter$$() Returns all nodes in footer
      • elem.modalFooter$$('button, [data-button]') Returns buttons in footer

Attributes 

NameDescriptionTypeDefaultRequired
descriptionSets the modal's accessible description.stringfalse
disable-closePrevents closing the modal by normal means like the ESC key, the close button, or `.close()`.booleanfalse
labelSets the modal's title and assistive device label.stringfalse
sizeHow much of the viewport width the modal uses. Supported values are small, medium, large, or full. You can't change the modal size after the modal is opened.stringmedium

Methods 

NameDescriptionArgument NameArgument TypeArgument Description
closeCloses the modal and resolves with an optional result.result*Value returned to the caller of `open()`. If the result argument is not provided, this method resolves with undefined and the modal is closed without a result.