A custom element for building accessible popup disclosure widgets and menu buttons.
Popup is an ill-defined term, but this popup element is intended to fulfill two specific use-cases:
- A disclosure widget where the disclosed content "pops up" as an overlay positioned adjacent to the trigger.
- A menu button where the popup content is a Menu element.
import { PopupElement } from 'inclusive-elements';
window.customElements.define('ui-popup', PopupElement);
<ui-popup placement="bottom-start">
<button type="button">
Open Popup <span aria-hidden="true">▾</span>
</button>
<div hidden>Popup content</div>
</ui-popup>
-
The first descendant that is a
<button>
or hasrole="button"
will be given thearia-expanded
attribute, which will reflect the open state of the popup. If the popup content hasrole="menu"
, then the popup button will also be given thearia-haspopup="true"
attribute. -
Clicking the button, or pressing the Down Arrow key while it is focused, will open the popup. The
hidden
attribute will be removed from the content element. The content element will be positioned next to the trigger element using Floating UI as per theplacement
attribute. -
The popup will be closed, and focus returned to the button, if:
- The backdrop outside of the popup content is clicked.
- The Escape key is pressed, or the user Tabs out of the popup.
- A child with a
role
beginning withmenuitem
is clicked.
const popup = document.querySelector('ui-popup');
// Programatically open and close the popup.
popup.open = true;
popup.addEventListener('open', callback);
popup.addEventListener('close', callback);
/* Optionally style the backdrop */
ui-popup::part(backdrop) {
}
/* Transitions can be applied to the popup using hello-goodbye */
@media (prefers-reduced-motion: no-preference) {
ui-popup > .enter-active,
ui-popup > .leave-active {
transition: all 0.5s;
}
ui-popup > .enter-from,
ui-popup > .leave-to {
opacity: 0;
transform: scale(0.5);
}
}