Accessible modal dialog component for React.JS applications with comprehensive ARIA support and customization options.
npx @tessl/cli install tessl/npm-react-modal@3.16.0React Modal is an accessible modal dialog component for React.JS applications. It provides comprehensive ARIA accessibility support, keyboard navigation, focus management, and extensive customization options for creating modal dialogs that work seamlessly with screen readers and assistive technologies.
npm install react-modalimport Modal from 'react-modal';For CommonJS:
const Modal = require('react-modal');import React, { useState } from 'react';
import Modal from 'react-modal';
// Set app element for accessibility (required)
Modal.setAppElement('#yourAppElement');
function App() {
const [modalIsOpen, setIsOpen] = useState(false);
function openModal() {
setIsOpen(true);
}
function closeModal() {
setIsOpen(false);
}
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
contentLabel="Example Modal"
>
<h2>Hello Modal</h2>
<button onClick={closeModal}>Close</button>
</Modal>
</div>
);
}React Modal uses a portal-based architecture that renders the modal outside the normal React component tree:
The main Modal component that renders an accessible dialog with overlay and content areas.
/**
* Accessible modal dialog component
*/
function Modal(props: ModalProps): JSX.Element;
interface ModalProps {
/** Required: Controls modal visibility */
isOpen: boolean;
/** Styling props */
style?: {
content?: React.CSSProperties;
overlay?: React.CSSProperties;
};
className?: string | ClassNameConfig;
overlayClassName?: string | ClassNameConfig;
/** Portal and DOM props */
portalClassName?: string;
bodyOpenClassName?: string;
htmlOpenClassName?: string;
parentSelector?: () => HTMLElement;
/** Accessibility props */
appElement?: HTMLElement | HTMLElement[] | NodeList | string;
ariaHideApp?: boolean;
role?: string;
contentLabel?: string;
aria?: { [key: string]: string };
/** Behavior props */
shouldFocusAfterRender?: boolean;
shouldCloseOnOverlayClick?: boolean;
shouldCloseOnEsc?: boolean;
shouldReturnFocusAfterClose?: boolean;
preventScroll?: boolean;
closeTimeoutMS?: number;
/** Event handlers */
onAfterOpen?: (options: { overlayEl: Element; contentEl: Element }) => void;
onAfterClose?: () => void;
onRequestClose?: (event: React.MouseEvent | React.KeyboardEvent) => void;
/** Refs and custom elements */
overlayRef?: (instance: HTMLDivElement) => void;
contentRef?: (instance: HTMLDivElement) => void;
overlayElement?: (props: any, contentEl: React.ReactElement) => React.ReactElement;
contentElement?: (props: any, children: React.ReactNode) => React.ReactElement;
/** Additional attributes */
data?: { [key: string]: string };
id?: string;
testId?: string;
children?: React.ReactNode;
}
interface ClassNameConfig {
base: string;
afterOpen: string;
beforeClose: string;
}Configuration methods available on the Modal component.
/**
* Sets the app element for accessibility. Must be called before using modals.
* @param element - DOM element, selector string, or array of elements to hide from screen readers
*/
Modal.setAppElement(element: HTMLElement | HTMLElement[] | NodeList | string): void;
/**
* Development-only: Sets custom HTML element creation function for testing (NODE_ENV !== "production")
* @param fn - Function to create HTML elements
*/
Modal.setCreateHTMLElement?(fn: (name: string) => HTMLElement): void;Pre-defined styling and constants available on the Modal component.
/**
* Default styles for modal overlay and content
*/
Modal.defaultStyles: {
overlay: {
position: 'fixed';
top: 0;
left: 0;
right: 0;
bottom: 0;
backgroundColor: 'rgba(255, 255, 255, 0.75)';
};
content: {
position: 'absolute';
top: '40px';
left: '40px';
right: '40px';
bottom: '40px';
border: '1px solid #ccc';
background: '#fff';
overflow: 'auto';
WebkitOverflowScrolling: 'touch';
borderRadius: '4px';
outline: 'none';
padding: '20px';
};
};const customStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
},
};
<Modal
isOpen={modalIsOpen}
style={customStyles}
contentLabel="Centered Modal"
>
<h2>Centered Content</h2>
</Modal><Modal
isOpen={modalIsOpen}
className="modal-content"
overlayClassName="modal-overlay"
contentLabel="Styled Modal"
>
<h2>Custom Styled Modal</h2>
</Modal><Modal
isOpen={modalIsOpen}
className={{
base: 'modal-content',
afterOpen: 'modal-content--after-open',
beforeClose: 'modal-content--before-close'
}}
overlayClassName={{
base: 'modal-overlay',
afterOpen: 'modal-overlay--after-open',
beforeClose: 'modal-overlay--before-close'
}}
>
<h2>Animated Modal</h2>
</Modal>// Required: Set app element before using any modals
Modal.setAppElement('#root');
// Or with multiple elements
Modal.setAppElement([document.getElementById('root'), document.getElementById('menu')]);<Modal
isOpen={modalIsOpen}
contentLabel="User Profile Dialog" // Required for screen readers
role="dialog" // Default, can be changed
ariaHideApp={true} // Default, hides background content
shouldFocusAfterRender={true} // Default, focuses modal on open
shouldReturnFocusAfterClose={true} // Default, returns focus on close
>
<h2 id="modal-title">User Profile</h2>
<div aria-describedby="modal-title">
Modal content here
</div>
</Modal>function handleRequestClose(event) {
// event can be from ESC key or overlay click
console.log('Modal close requested:', event.type);
setModalIsOpen(false);
}
<Modal
isOpen={modalIsOpen}
onRequestClose={handleRequestClose}
shouldCloseOnOverlayClick={true} // Default
shouldCloseOnEsc={true} // Default
>
<h2>Closeable Modal</h2>
</Modal>function handleAfterOpen({ overlayEl, contentEl }) {
// Modal is now fully open and accessible
console.log('Modal opened', { overlayEl, contentEl });
}
function handleAfterClose() {
// Modal is completely closed and removed from DOM
console.log('Modal closed');
}
<Modal
isOpen={modalIsOpen}
onAfterOpen={handleAfterOpen}
onAfterClose={handleAfterClose}
>
<h2>Modal with Lifecycle Handlers</h2>
</Modal>const customOverlay = (props, contentEl) => (
<div {...props} className="custom-overlay">
{contentEl}
</div>
);
const customContent = (props, children) => (
<div {...props} className="custom-content">
<header>Modal Header</header>
<main>{children}</main>
<footer>Modal Footer</footer>
</div>
);
<Modal
isOpen={modalIsOpen}
overlayElement={customOverlay}
contentElement={customContent}
>
<p>Content goes in the main section</p>
</Modal><Modal
isOpen={modalIsOpen}
closeTimeoutMS={200} // Wait 200ms before unmounting
className={{
base: 'modal',
afterOpen: 'modal--after-open',
beforeClose: 'modal--before-close'
}}
>
<h2>Animated Modal</h2>
</Modal>Corresponding CSS:
.modal {
opacity: 0;
transition: opacity 200ms;
}
.modal--after-open {
opacity: 1;
}
.modal--before-close {
opacity: 0;
}<Modal
isOpen={modalIsOpen}
portalClassName="custom-portal"
parentSelector={() => document.getElementById('modal-root')}
bodyOpenClassName="body-modal-open"
htmlOpenClassName="html-modal-open"
>
<h2>Custom Portal Modal</h2>
</Modal>let overlayRef;
let contentRef;
<Modal
isOpen={modalIsOpen}
overlayRef={ref => overlayRef = ref}
contentRef={ref => contentRef = ref}
onAfterOpen={() => {
// Direct DOM access available
console.log('Overlay element:', overlayRef);
console.log('Content element:', contentRef);
}}
>
<h2>Modal with Refs</h2>
</Modal><Modal
isOpen={modalIsOpen}
testId="user-profile-modal"
contentLabel="User Profile"
>
<h2>User Profile Modal</h2>
<p>This modal can be found by test frameworks using data-testid="user-profile-modal"</p>
</Modal>