MediumSquare
Programming

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.

120

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 cancel event you can intercept.
  • A ::backdrop pseudo-element appears, styleable with plain CSS.
  • The dialog renders in the top layer, so no more z-index: 99999 arms 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.

Photo by Amper on Unsplash
Photo by Amper on Unsplash

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.

Photo by Compagnons on Unsplash
Photo by Compagnons on Unsplash

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.

120
7 people clapped
Written byMatteo Rossi

Frontend developer in Milan. Browsers, performance and the web platform.

More to read