Much like precious gems, the React components we use have been crafted in the mines of our frontend chapter under the pressure of heavy traffic, high technical and even higher a11y standards. The years have smoothed their edges and hardened their resolve. They are unit-tested, design- and UX-approved bits of battle-tested and resilient vigor.
That’s nice; also boring. As front-end developers, we sometimes just need to sacrifice all of this hard-earned merit on the shrine of the new and shiny thing, for no real reason in particular.
At least that’s what I did, when I decided it was time to ignore the pre-existing, rock-solid Modal component in favour of implementing a new component based on the — now widely supported — HTML-native <dialog> element.

The shiny new thing is the native web
After a decade of everybody and their mum trying to build layers upon layers upon layers upon layers on top of the web platform, just to abstract some of its kinks away, we are now going the other way: The more native, the better.
Gone are the days of abusing the z-index to conditionally render a mere div as a backdrop for the other div that holds your other divs, which make up the contents of your modal.
Gone are the days of trying to figure out the appropriate suite of aria-labels and tab-indices to accompany all of that.
Gone are the days of hacking together a handleClickOutside function inside React components, with useRef or maybe even — Lord have mercy — a forwardRef and a couple of useEffects, to figure out when the DOM has actually updated.
Gone are the days of loading remote content in your modal multiple times whenever a user opens and closes it.
Now, there is out-of-the-box web-native support for this. The <dialog> element comes with its own ::backdrop pseudo-selector, and it’s rendered on top of your page by default. So something like this should suffice:
// jsx instead of tsx for brevity
const Modal = ({ children }) => {
const dialogRef = useRef(null);
useEffect(() => {
if (isOpen && dialogRef.current?.showModal) {
dialogRef.current.showModal();
} else if (dialogRef.current?.close) {
dialogRef.current.close();
}
}, [isOpen]);
return <dialog ref={dialogRef}>{children}</dialog>;
};
Easy, right? Top-notch a11y is also included. With one exception, that is, as focus-trapping is still an issue that needs to be addressed with each integration. No dealbreaker, I thought, and slapped this solution together:
// jsx instead of tsx for brevity
const Modal = ({ children, header }) => {
const dialogRef = useRef(null);
const describedById = `${header}-modal-description`;
const trapFocusAtStart = () => {
if (!dialogRef.current) return;
// bring the focus back to the last tab-able element inside the modal
// (actually the second to last, because the last one is the other focus trap)
const elements = dialogRef.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',);
const element = elements[elements.length - 2];
if (element instanceof HTMLElement) {
element.focus();
}
};
const trapFocusAtEnd = () => {
if (!closeButtonRef.current) return;
// focus the close button, as it will always be the first tab-able element
closeButtonRef.current.focus();
};
useEffect(() => {
if (isOpen && dialogRef.current?.showModal) {
dialogRef.current.showModal();
} else if (dialogRef.current?.close) {
dialogRef.current.close();
}}, [isOpen]);
return (
<dialog ref={dialogRef}>
{/* This div fulfills two purposes:
1. It is the target for the focus trap, so that the user can't (shift) tab out of the modal
2. It is the target for the screen reader to announce the content of the modal
*/}
<div
id={describedById}
tab-index={0}
position="absolute" // let's pretend it just works like this
opacity={0}
onFocus={trapFocusAtStart}
>
Anfang des Dialogfensters "{header}". Escape bricht ab und schließt das Dialogfenster.
</div>
{children}
{/* another focus trap on the other side of the modal */}
<div
tabIndex={0}
position="absolute"
opacity={0}
onFocus={trapFocusAtEnd}
/>
</dialog>
);
};
After this minor inconvenience is solved, it is definitely too late to second guess whether this is actually simpler than the existing solution and ship it.

Did you hear that? Chekhov’s gun is firing.
Gone are the days of loading remote content in your modal multiple times whenever a user opens and closes it.
I built this new Modal partly because I wanted to use it for a new addition to the real estate part of our product. Shadowmap is a relatively young Austrian startup with an impressive product, visualizing sunlight and shadows for all of Austria (and beyond). Valuable information, if you are looking to rent or buy a new place for your plants and/or cats. We signed a cooperation with them to leverage the undeniable value their product brings, with our reach.
No matter which way you look at it, the number of people who will open the Shadowmap on our site would mean tremendous traffic on their infrastructure. It didn’t help that there was a crucial oversight on my part: There is no conditional rendering. The dialog element is just another part of the DOM. It — and its contents — get loaded on every page load, and so did the Shadowmap.

What resulted in an arguably better user experience, since the contents were already (at least partially) loaded when the users opened the modal, wreaked unintended havoc on our new partner’s infrastructure.
What I learned (again)
I wish I could say that there are lessons in that story that I learned for the first time, but truthfully, they are only lessons I learn over and over again. You might call it ‘Fifty Shades of Shiny’. I know that the safe path is more often than not the sane path. Regardless, every so often, the gravitational force of doing something new in the front-end gets the better of me, complete with all the naughty and exciting pain that comes with it.
Friendly Fire DDoS Attack by Omission was originally published in willhaben Tech Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.