Lightning Web Component Modal
An example of how to implement a composable modal LWC, which can be used in any project. To read the whole blog post about putting this together, please refer to The Joys of Apex: Composable Modal post. The next post also ties together several points related to <slot>
based composition in LWC, as well as how to write good unit tests for your Lightning Web Components.
Modal Usage
Included in this repository is the modal
as well as an example modal_wrapper
component that hooks into the modal
. The modal utilizes the following slots/properties to dynamically show content (properties are annotated typescript-style, where a question mark indicates that the property is optional) :
- (property -
string?
)modal-header
: use to display a title for your modal - (property -
string?
)modal-tagline
: use to display a subtitle for your modal - (property -
function?
)modalSaveHandler
can be passed to the modal to display a save button. Injecting this function into the modal requires the use of a fat-arrow function, as demonstrated in themodal_wrapper
example - (slot - name = "body") this slot is to be used for any and all LWC content that is to be "hidden" and marked inaccessible when the modal is open
- (slot - name = "modalContent") this slot is used to inject markup into the body of the modal. In the
modal_wrapper
example below, you can see two standard text-basedlightning-input
components, as well as a date-typelightning-input
being passed to the modal - (public method)
toggleModal
- themodal_wrapper
example makes use of a button whoseonclick
method calls the modal to open it
What Does It End Up Looking Like?
This is what the modal_wrapper
component looks like when added to a flexipage:
Accessibility
As much as possible, this modal attempts to adhere to the guidelines set down for modals in the Lightning Design System:
Modals, by definition, trap focus. This means that if a user presses Tab or Shift+Tab while their keyboard focus is in the modal, their focus should stay in and cycle through the modal’s content. Focus shouldn’t return to the underlying page until the user presses the Esc key, presses an explicit “Cancel” or “Close” button in the modal, or performs another action that closes the modal.
Credit for properly trapping focus within the modal goes to Suraj Pillai. The "issue" with LWC / Web Components in general at the moment is that the Shadow DOM is supposed to prevent a component from knowing anything about the DOM except what's present in the component - but slot-based composition, the ideal way to compose a modal that can be used by any component, isn't presently a first-class citizen for detecting focusable elements that have been injected by means of <slot>
markup. I have filed a Github issue with the LWC team trying to address this, but in the meantime, focus-trapping can still be achieved.
Additionally, this modal meets all of the criteria for the LDS's expected keyboard interactions:
- Esc key closes the modal and moves focus to whatever triggered the modal to open
- Tab key at bottom of modal cycles focus back to first focusable element at top of modal
- Shift+Tab keys at top of modal cycle focus back to last focusable element at bottom of modal
- Enter key submits modal’s form data, if applicable
- Clicking outside of the modal closes the modal
Closing
In practice, the use of a modal is either to display optional information (in which case the modalSaveHandler
function is unnecessary; you only need to give people options for how to close the modal), or to block progress until a form/required fields are filled out. Either way, this modal recipe gives you everything you need to succeed!
Contributions
- many thanks to reader and SFXD Discord frequenter havana59er for his contributions to the article. His investigation into assigning the
tabindex
property to different sections of the modal, additionalhandleModalLostFocus
handler, and short-circuit feedback forrenderedCallback
were all excellent. I'm much obliged, and the modal is better off! - hats off to Justin Lyon, another SFXD Discord frequenter and fellow LWC enthusiast for experimenting with his own modal. He managed to shave off one of the existing
window
event listeners by the use of explicit classes to determine when the modal should be closed. The post has been updated to reflect this; however, I leave the original solution below because I believe thatgetBoundingClientRect()
is something you should know about when considering your options for examining the size of a contiguous DOM section! - the code has been cleaned up considerably by SFXD Discord legend, Pseudobinary, aka'd as PSB. His focus-trapping solution is a huge success!