I Replaced Our Modal Library With the Native Dialog Element
Focus trapping, the Escape key and a backdrop, all built in. Here is what worked and what still needed care.
Every frontend codebase I have worked on has had a modal library, and every one of those libraries has had a bug ticket about focus. Focus escaping behind the overlay. Focus lost when the modal closes. Screen readers announcing the page underneath. This year I tried the browser's own dialog element in a real product, and I am not going back.
What you get for free
Calling showModal() on a dialog element gives you behaviour that used to take a few hundred lines:
- The rest of the page becomes inert, so clicks and tab presses cannot reach it.
- The Escape key closes the dialog and fires a
cancelevent you can intercept. - A
::backdroppseudo-element appears, styleable with plain CSS. - The dialog renders in the top layer, so no more
z-index: 99999arms races.
const dialog = document.querySelector('#confirm-delete');
openButton.addEventListener('click', () => dialog.showModal());
dialog.addEventListener('close', () => {
if (dialog.returnValue === 'confirm') deleteItem();
});
A form inside the dialog with method='dialog' closes it on submit and sets returnValue to the button's value. That alone deleted a small state machine from our code.
What still needed care
It was not entirely free. Three things tripped us up.
Returning focus
Browsers have improved here, but I still explicitly return focus to the button that opened the dialog. It costs two lines and removes any doubt.
Closing on backdrop click
There is no built-in option for 'click outside to close'. We listen for clicks on the dialog itself and check whether the click landed outside its content box. Simple, but you have to know to do it.
Scroll locking
The page behind can still scroll on some setups. A small CSS rule on the root element while the dialog is open sorted it.
The numbers
Our old library was about 11KB gzipped with its dependencies. The replacement is a 40-line wrapper. More importantly, our accessibility audit came back with zero dialog-related issues for the first time in three years.
The broader point
The web platform has been absorbing patterns we used to build ourselves: dialogs, popovers, lazy images, smooth scrolling. Each one is an invitation to delete code. Not every native feature is ready for every product, but it is worth checking every year or so. The platform keeps quietly doing our homework for us.
Cover: Photo by Joshua Reddekopp on Unsplash.