🛠️ Modal: Closing the modal
The modal can be closed in three ways:
Using the Escape key
Clicking the close button
Clicking outside the modal
We added event listeners for these three ways in our previous code:
// Previous version
modalCloseButton.addEventListener('click', event => {
closeModal()
})
modalOverlay.addEventListener('click', event => {
if (!event.target.closest('.modal')) {
closeModal()
}
})
document.addEventListener('keydown', event => {
if (isModalOpen() && event.key === 'Escape') {
closeModal()
}
})
We need to add these event listeners to Modal
as well.
We’ll do it in this order:
Add an event listener for the close button
Make sure we can close the modal
Add other event listeners
Adding event listener for the close button is easy. First, we search for the close button. Then, we add an event listener to it.
export default function Modal (settings) {
// Declare variables
const closeButtonElement = modalElement.querySelector('.jsModalClose')
const modal = {/* ... */}
// Add event listeners
closeButtonElement.addEventListener('click', _ => {/*...*/})
}
We need to close the modal in this event listener. Here, we can use a close
method.
export default function Modal (settings) {
// Declare variables
const modal = {
// Other methods
close () {
console.log('Closing modal')
}
}
// Add event listeners
closeButtonElement.addEventListener('click', _ => modal.close())
}
Your browser doesn't support embedded videos. Watch the video here instead.
Closing the modal
There are five steps to close the modal:
Removing is-open
Removing the focus trap
Focusing on the button
Setting aria-expanded
back to false
Removing aria-hidden
from other elements
We can see these five steps clearly from the code we wrote previously.
// Previous code for closeModal
const closeModal = _ => {
document.body.classList.remove('modal-is-open')
document.removeEventListener('keydown', trapFocus)
modalButton.focus()
modalButton.setAttribute('aria-expanded', false)
main.removeAttribute('aria-hidden')
}
Removing is-open
We need to remove is-open
from overlayElement
to close the modal.
export default function Modal (settings) {
// Declare variables
const modal = {
// Other methods
close () {
overlayElement.classList.remove('is-open')
}
}
// Add event listeners
}
Your browser doesn't support embedded videos. Watch the video here instead.
Removing Focus Trap
We can remove the focus trap by removing the event listener.
export default function Modal (settings) {
// Declare variables
const modal = {
// Other methods
close () {
// ...
document.removeEventListener('keydown', trapFocus)
}
}
// Add event listeners
}
Next, we focus on the button element.
export default function Modal (settings) {
// Declare variables
const modal = {
// Other methods
close () {
// ...
buttonElement.focus()
}
}
// Add event listeners
}
Your browser doesn't support embedded videos. Watch the video here instead.
Focus returned to the button
Setting aria-expanded
back to false
This should be simple for you.
export default function Modal (settings) {
// Declare variables
const modal = {
// Other methods
close () {
// ...
buttonElement.setAttribute('aria-expanded', false)
}
}
// Add event listeners
}
Removing aria-hidden
from other elements
Finally, we need to remove aria-hidden
from other elements.
Here, we need to find other elements, loop through them, and remove the aria-hidden
attribute.
export default function Modal (settings) {
// Declare variables
const modal = {
// Other methods
close () {
// ...
const overlaySiblingEls = [...overlayElement.parentElement.children]
.filter(el => el !== overlayElement)
overlaySiblingEls.forEach(element => {
element.removeAttribute('aria-hidden')
})
}
}
// Add event listeners
}
Your browser doesn't support embedded videos. Watch the video here instead.
Simplifying code
The part where we find sibling elements, loop through them, and add (or remove) aria-hidden can be quite difficult to read. We can create methods to make them easier to understand.
First, we create a hideSiblingElements
method to add aria-hidden
to sibling elements.
export default function Modal (settings) {
// Declare variables
const modal = {
// Other methods
hideSiblingElements () {
const overlaySiblingEls = [...overlayElement.parentElement.children]
.filter(el => el !== overlayElement)
overlaySiblingEls.forEach(element => {
element.setAttribute('aria-hidden', true)
})
}
}
// Add event listeners
}
We use it like this:
export default function Modal (settings) {
// Declare variables
const modal = {
// Other methods
open () {
// ...
modal.hideSiblingElements()
}
}
// Add event listeners
}
Then we create a showSiblingElements
method to remove aria-hidden
from sibling elements.
export default function Modal (settings) {
// Declare variables
const modal = {
// Other methods
showSiblingElements () {
const overlaySiblingEls = [...overlayElement.parentElement.children]
.filter(el => el !== overlayElement)
overlaySiblingEls.forEach(element => {
element.removeAttribute('aria-hidden')
})
},
}
// Add event listeners
}
We use showSiblingElements
like this:
export default function Modal (settings) {
// Declare variables
const modal = {
// Other methods
close () {
// ...
modal.showSiblingElements()
},
}
// Add event listeners
}
Since hideSiblingElements
and showSiblingElements
use the same code to get sibling elements, we can use a getter function to make them easier to understand.
export default function Modal (settings) {
// Declare variables
const modal = {
get siblingElements () {
return [...overlayElement.parentElement.children]
.filter(el => el !== overlayElement)
},
showSiblingElements () {
modal.siblingElements.forEach(element => {
element.removeAttribute('aria-hidden')
})
},
hideSiblingElements () {
modal.siblingElements.forEach(element => {
element.setAttribute('aria-hidden', true)
})
},
// Other methods
}
// Add event listeners
}
Other event listeners
We need to add two more event listeners:
One to close the modal when we click on the overlay
One to close the modal when we press Escape
Here’s the previous code:
// Previous code
modalOverlay.addEventListener('click', event => {
if (!event.target.closest('.modal')) {
closeModal()
}
})
document.addEventListener('keydown', event => {
if (isModalOpen() && event.key === 'Escape') {
closeModal()
}
})
We’ll start with the first one.
Closing modal when clicking on the overlay element
We need to add a click
event to the overlayElement
. We can copy the code we wrote from our previous version. Remember to change closeModal
to modal.close
.
export default function Modal (settings) {
// Declare variables
const modal = {/* ... */}
// Add event listeners
overlayElement.addEventListener('click', event => {
if (!event.target.closest('.modal')) {
modal.close()
}
})
}
Closing the modal with the Escape key
We can copy-paste the event listener into Modal
. Again, remember to change closeModal
to modal.close
.
export default function Modal (settings) {
// Declare variables
const modal = {/* ... */}
// Add event listeners
document.addEventListener('keydown', event => {
if (isModalOpen() && event.key === 'Escape') {
modal.close()
}
})
}
We don’t have an isModalOpen
function anymore. But we can create a getter function to check whether the modal is open.
export default function Modal (settings) {
// Declare variables
const modal = {
get isOpen () {
return overlayElement.classList.contains('is-open')
}
}
// Add event listeners
}
We can use the isOpen
getter function like this:
export default function Modal (settings) {
// Declare variables
const modal = {/* ... */}
// Add event listeners
document.addEventListener('keydown', event => {
if (modal.isOpen && event.key === 'Escape') {
modal.close()
}
})
}