How to detect a click outside an element
Short answer
Listen to clicks on the whole document and check with contains() whether the click was inside the element:
<button id="toggle">Menu</button>
<ul id="menu" hidden>
<li>Profile</li>
<li>Settings</li>
</ul>
<script>
const menu = document.getElementById('menu');
const toggle = document.getElementById('toggle');
toggle.addEventListener('click', () => {
menu.hidden = !menu.hidden;
});
document.addEventListener('click', (event) => {
if (!menu.contains(event.target) && !toggle.contains(event.target)) {
menu.hidden = true;
}
});
</script>With closest
closest() finds the nearest parent that matches a selector. It is handy when there are several dropdowns:
document.addEventListener('click', (event) => {
if (!event.target.closest('.dropdown')) {
document.querySelectorAll('.dropdown.open').forEach((el) => el.classList.remove('open'));
}
});The Escape key
Users expect a popup to close on Escape too:
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') menu.hidden = true;
});Common mistake
Without the check for the button, a click on the button opens the menu, and the same click reaches the document and closes it at once. Exclude the button or stop the event with event.stopPropagation().
toggle.addEventListener('click', (event) => {
event.stopPropagation();
menu.hidden = !menu.hidden;
});