I'm building a custom dropdown. The real thing is going to have all the necessary ARIA attributes of course, but here's the barebones version:
[...document.querySelectorAll('.select')].forEach(select => {
select.addEventListener('click', function() {
select.nextElementSibling.classList.toggle('visible');
});
});
.dropdown {
position: relative;
width: 16rem;
margin-bottom: 2rem;
}
.select {
display: flex;
align-items: center;
height: 2rem;
background-color: #ccc;
}
.popup {
display: none;
position: absolute;
left: 0;
right: 0;
height: 10rem;
background-color: #eee;
box-shadow: 0 0 0.5rem red;
}
.popup.visible {
display: block;
}
<!doctype html>
<html>
<body>
<div class="dropdown">
<div class="select">Button 1 ▼</div>
<div class="popup">Popup 1</div>
</div>
<div class="dropdown">
<div class="select">Button 2 ▼</div>
<div class="popup">Popup 2</div>
</div>
</body>
</html>
The obvious issue is that, when you open the first dropdown, popup 1 appears behind button 2. The obvious solution would be to give .popup
a z-index
, and make it an absurdly large value like 999
to make sure it appears above other elements on the page as well.
However, in my case, I would also like the popup to appear behind its corresponding button (in order to hide its box-shadow
).
If I give the button a z-index
greater than the popup's, the original problem returns: popup 1 appears behind button 2. If I instead give the z-index: 999
to the entire .dropdown
and create a new stacking context, the same thing happens.
Is there any way I can meet my two requirements at the same time (popup behind its button, and only that one, but above everything else on the page)?
You could track the dropdown .open
state. And use that to toggle the display
property of its child .popup
. However the .dropdown.open
state will have a z-index:1
, that way it will always show up on top of elements below it.
[...document.querySelectorAll('.select')].forEach(select => {
select.addEventListener('click', function(event) {
const {
target: {
parentElement: activeDropdown
}
} = event;
activeDropdown.classList.toggle('open');
const container = select.parentElement.parentElement;
const dropdowns = container.querySelectorAll('.dropdown');
Array.from(dropdowns).forEach(item => {
if (item !== activeDropdown) {
item.classList.remove('open')
}
})
});
});
.dropdown {
position: relative;
width: 16rem;
margin-bottom: 2rem;
}
.dropdown.open {
z-index: 1;
}
.dropdown.open>.popup {
display: block;
}
.select {
display: flex;
align-items: center;
height: 2rem;
background-color: #ccc;
}
.popup {
display: none;
position: absolute;
left: 0;
right: 0;
height: 10rem;
background-color: #fff;
box-shadow: 0 0 0.5rem red;
}
<!doctype html>
<html>
<body>
<div class="dropdown">
<div class="select">Button 1 ▼</div>
<div class="popup">Popup 1</div>
</div>
<div class="dropdown">
<div class="select">Button 2 ▼</div>
<div class="popup">Popup 2</div>
</div>
</body>
</html>