lwc-modal
lwc-modal copied to clipboard
Accessible, composable modal for Salesforce.com (SFDC)'s Lightning Web Components (LWC) system
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?)modalSaveHandlercan 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_wrapperexample - (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_wrapperexample below, you can see two standard text-basedlightning-inputcomponents, as well as a date-typelightning-inputbeing passed to the modal - (public method)
toggleModal- themodal_wrapperexample makes use of a button whoseonclickmethod 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
tabindexproperty to different sections of the modal, additionalhandleModalLostFocushandler, and short-circuit feedback forrenderedCallbackwere 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
windowevent 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!