Accessibility in modal dialog
Generated with AI
🗓️ May 7th, 2022

How To Build An Accessible Modal

Modal window (or dialog box) is one of those classic element composing an interface. Every app or website has a modal. But it’s surprisingly hard to get it right.

Accessibility Concerns About Modals

I used to think that building a modal in React is just a matter of showing a box when a variable is set to true (after a click on a button for example). But there are many things, accessibility related, to consider :

Using Headless Modal Component

You could try to implement a modal from scratch. But I strongly advise you against that (at least for production code).

In React, there are many headless component libraries that help developers build accessible interfaces. Headless components are a type of unstyled components that are fully functional.

You can find below few examples of headless modal libraries

React modal is clearly the most popular out there. But I think it is not the best option, because you can disable some accessibility features needed for a modal.

For the other libraries, important accessibility features are enabled by default. Each of them is a good solution but I have a preference for Reach UI dialog for its ease of use (simple API and component easily style-able).

Reach UI Dialog Quickstart


Terminal window
yarn add @reach/dialog
# or
npm install @reach/dialog


In this example, I use Stitches. But you can use any CSS-in-JS library of your choice. You can also use CSS modules to style DialogOverlay and DialogContent

import { DialogOverlay, DialogContent } from "@reach/dialog";
import { styled } from "path/to/stitches.config.ts";
export const Overlay = styled(DialogOverlay, {
backgroundColor: "hsl(0 0% 0% / 0.439)",
position: "fixed",
top: 0,
bottom: 0,
left: 0,
right: 0,
display: "grid",
placeItems: "center",
export const Content = styled(DialogContent, {
backgroundColor: "white",
padding: "1rem",
minWidth: 300,
borderRadius: 20,


import { useState } from "react";
import { Content, Overlay } from "./reach-dialog.ts";
const ReachDialogDemo = () => {
const [isOpen, setIsOpen] = useState(false);
const show = () => setIsOpen(true);
const close = () => setIsOpen(false);
return (
<h1>Reach Dialog Demo</h1>
<button onClick={show}>Open Dialog</button>
<Overlay onDismiss={close} isOpen={isOpen}>
<h2>Reach dialog</h2>
<p>Hello World</p>
<button onClick={close}>Close</button>
export default ReachDialogDemo;

What About The Official dialog Element ?

Although major browsers support it recently, there are still accessibility issues that are being discussed. I think, reaching for a solution like headless components is totally valid for now.