A simple responsive and accessible react modal.
- Focus trap inside the modal.
- Centered modals.
- Scrolling modals.
- Multiple modals.
- Accessible modals.
- Easily customizable via props.
- Typescript support
- Small bundle size
Installation
Inside your project directory, install react-responsive-modal by running the following:
npm install react-responsive-modal --save
# Or with yarn
yarn add react-responsive-modal
Usage
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import 'react-responsive-modal/styles.css';
import { Modal } from 'react-responsive-modal';
const App = () => {
const [open, setOpen] = useState(false);
const onOpenModal = () => setOpen(true);
const onCloseModal = () => setOpen(false);
return (
<div>
<button onClick={onOpenModal}>Open modal</button>
<Modal open={open} onClose={onCloseModal} center>
<h2>Simple centered modal</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet
hendrerit risus, sed porttitor quam.
</p>
</Modal>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('app'));
- If you are using Next.js, you need to import the styles in
pages/_app.jsorpages/_app.tsx. - If you are using Create React App, you need to import the styles in
index.jsorindex.tsx.
💡 When you integrate react-responsive-modal in your app, make sure that your Modal content is wrapped in an element so the close icon is not shown on top.
Multiple modals
You can render multiple modals at the same time. Clicking on the overlay or pressing "esc" will only close the last modal.
import React from 'react';
import { Modal } from 'react-responsive-modal';
const App = () => {
const [openFirst, setOpenFirst] = React.useState(false);
const [openSecond, setOpenSecond] = React.useState(false);
const littleLorem = (
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar
risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit
risus, sed porttitor quam.
</p>
);
return (
<>
<button className="button" onClick={() => setOpenFirst(true)}>
Open first modal
</button>
<Modal open={openFirst} onClose={() => setOpenFirst(false)} center>
<p>First modal</p>
{littleLorem}
<button className="button" onClick={() => setOpenSecond(true)}>
Open second modal
</button>
</Modal>
<Modal open={openSecond} onClose={() => setOpenSecond(false)} center>
<p>Second modal</p>
{littleLorem}
</Modal>
</>
);
};
export default App;
Modal with a lot of content
When a modal with a content overflowing the window size, you can scroll the content of the modal but you will see that the body scroll is locked until you actually close the modal.
import React from 'react';
import { Modal } from 'react-responsive-modal';
const App = () => {
const [open, setOpen] = React.useState(false);
const lorem = (
<p>
Mauris ac arcu sit amet dui interdum bibendum a sed diam. Praesent rhoncus
congue ipsum elementum lobortis. Ut ligula purus, ultrices id condimentum
quis, tincidunt quis purus. Proin quis enim metus. Nunc feugiat odio at
eros porta, ut rhoncus lorem tristique. Nunc et ipsum eu ex vulputate
consectetur vel eu nisi. Donec ultricies rutrum lectus, sit ame feugiat
est semper vitae. Proin varius imperdiet consequat. Proin eu metus nisi.
In hac habitasse platea dictumst. Vestibulum ac ultrices risus.
Pellentesque arcu sapien, aliquet sed orci sit amet, pulvinar interdum
velit. Nunc a rhoncus ipsum, maximus fermentum dolor. Praesent aliquet
justo vitae rutrum volutpat. Ut quis pulvinar est.
</p>
);
return (
<>
<button className="button" onClick={() => setOpen(true)}>
Open big modal
</button>
<Modal open={open} onClose={() => setOpen(false)}>
<h2>Big modal</h2>
{lorem}
{lorem}
{lorem}
{lorem}
{lorem}
{lorem}
{lorem}
{lorem}
{lorem}
</Modal>
</>
);
};
export default App;
Focus Trapped modal
By default, when the modal open, the first focusable element will be focused. Press Tab to navigate between the focusable elements in the modal. You can notice that when the modal is open, you can't focus the elements outside of it.
If you want to disable this behavior, set the focusTrapped prop to false.
import React from 'react';
import { Modal } from 'react-responsive-modal';
const App = () => {
const [open, setOpen] = React.useState(false);
return (
<>
<button className="button" onClick={() => setOpen(true)}>
Open modal
</button>
<Modal open={open} onClose={() => setOpen(false)}>
<h2>Try tabbing/shift-tabbing thru elements</h2>
<form action="">
<p>
<label htmlFor="firstName">
First name (I should be focused be default)
<input type="text" />
</label>
</p>
<p>
<label htmlFor="lastName">
Last name
<input type="text" />
</label>
</p>
<button>test</button>
<input type="submit" value="Submit" />
</form>
</Modal>
</>
);
};
export default App;
Focus Trapped initial focus
You can also set to trap focus within the modal, but decide where to put focus when opened. To do this use initialFocusRef prop and set it to a ref of an element you want to focus. In this example we focus on the modal root element.
import React, { useRef } from 'react';
import { Modal } from 'react-responsive-modal';
const App = () => {
const [open, setOpen] = React.useState(false);
const modalRef = useRef<HTMLDivElement>(null);
return (
<>
<button className="button" onClick={() => setOpen(true)}>
Open modal
</button>
<Modal
ref={modalRef}
open={open}
onClose={() => setOpen(false)}
initialFocusRef={modalRef}
>
<h2>Try tabbing/shift-tabbing thru elements</h2>
<form action="">
<p>
<label htmlFor="firstName">
First name (I shouldn't be focused - press Tab to focus me)
<input type="text" />
</label>
</p>
<p>
<label htmlFor="lastName">
Last name
<input type="text" />
</label>
</p>
<button>test</button>
<input type="submit" value="Submit" />
</form>
</Modal>
</>
);
};
export default App;
Custom styling with css
Customising the Modal style via css is really easy. For example if you add the following css to your app you will get the following result:
/* examples/custom-styling.css */
.customOverlay {
background: rgba(36, 123, 160, 0.7);
}
.customModal {
background: #b2dbbf;
max-width: 500px;
width: 100%;
}
import React from 'react';
import { Modal } from 'react-responsive-modal';
const App = () => {
// import './examples/custom-styling.css';
const [open, setOpen] = React.useState(false);
return (
<>
<button className="button" onClick={() => setOpen(true)}>
Open modal
</button>
<Modal
open={open}
onClose={() => setOpen(false)}
center
classNames={{
overlay: 'customOverlay',
modal: 'customModal',
}}
>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet
hendrerit risus, sed porttitor quam.
</p>
</Modal>
</>
);
};
export default App;
Custom animation
If you want to change the default animation, you can do so by creating your own css animation. The modal and the overlay can be animated separately. For example if you add the following css to your app you will get the following result:
/* examples/custom-animation.css */
@keyframes customEnterOverlayAnimation {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes customLeaveOverlayAnimation {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes customEnterModalAnimation {
0% {
transform: scale(0.2);
}
100% {
transform: scale(1);
}
}
@keyframes customLeaveModalAnimation {
0% {
transform: scale(1);
}
100% {
transform: scale(0.2);
}
}
import React from 'react';
import { Modal } from 'react-responsive-modal';
const App = () => {
// import './examples/custom-animation.css';
const [open, setOpen] = React.useState(false);
return (
<>
<button className="button" onClick={() => setOpen(true)}>
Open modal
</button>
<Modal
open={open}
onClose={() => setOpen(false)}
center
classNames={{
overlayAnimationIn: 'customEnterOverlayAnimation',
overlayAnimationOut: 'customLeaveOverlayAnimation',
modalAnimationIn: 'customEnterModalAnimation',
modalAnimationOut: 'customLeaveModalAnimation',
}}
animationDuration={800}
>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet
hendrerit risus, sed porttitor quam.
</p>
</Modal>
</>
);
};
export default App;
If you want to apply a custom animation to the modal body you can do like this:
Custom close icon
You can customise the close icon used by the Modal by providing your own image or svg.
import React from 'react';
import { Modal } from 'react-responsive-modal';
const App = () => {
const [open, setOpen] = React.useState(false);
const closeIcon = (
<svg fill="currentColor" viewBox="0 0 20 20" width={28} height={28}>
<path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clipRule="evenodd"
></path>
</svg>
);
return (
<>
<button className="button" onClick={() => setOpen(true)}>
Open modal
</button>
<Modal
open={open}
onClose={() => setOpen(false)}
center
closeIcon={closeIcon}
>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet
hendrerit risus, sed porttitor quam.
</p>
</Modal>
</>
);
};
export default App;
Custom container
By default, the Modal will be rendered at the end of the html body tag. If you want to render the Modal in your own container, you can pass your own Element to the container prop.
import React from 'react';
import { Modal } from 'react-responsive-modal';
const App = () => {
const [open, setOpen] = React.useState(false);
const myRef = React.useRef(null);
return (
<>
<div ref={myRef} />
<button className="button" onClick={() => setOpen(true)}>
Open modal
</button>
<Modal
open={open}
onClose={() => setOpen(false)}
center
container={myRef.current}
>
<p>
Take a look with the devtools, you can see that the modal is inside
the div we are targeting and not at the end of the body tag.
</p>
</Modal>
</>
);
};
export default App;
Accessibility
- Follow the ARIA best practices by using either:
ariaLabelledbyandariaDescribedbyprops to reference visible content, ORariaLabelprop for a simple accessible label
// Option 1: Using ariaLabelledby and ariaDescribedby
<Modal
open={open}
onClose={onCloseModal}
ariaLabelledby="my-modal-title"
ariaDescribedby="my-modal-description"
>
<h2 id="my-modal-title">My Title</h2>
<p id="my-modal-description">My Description</p>
</Modal>
// Option 2: Using ariaLabel
<Modal
open={open}
onClose={onCloseModal}
ariaLabel="Modal Title"
>
<h2>Preferences</h2>
<p>Update your settings below</p>
</Modal>
aria-modalis set to true automatically.- When the modal is open the focus is trapped within it.
- Clicking on the overlay closes the Modal.
- Pressing the "Esc" key closes the Modal.
- When the modal is open the page scroll is blocked for the elements behind the modal.
- Closing the modal will unblock the scroll.
- The modal is rendered in a portal at the end of the
body.
Props
| Name | Type | Default | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --- |
| open* | boolean | | Control if the modal is open or not. |
| center | boolean | false | Should the dialog be centered. |
| closeOnEsc | boolean | true | Is the modal closable when user press esc key. |
| closeOnOverlayClick | boolean | true | Is the modal closable when user click on overlay. |
| blockScroll | boolean | true | Whether to block scrolling when dialog is open. |
| showCloseIcon | boolean | true | Show the close icon. |
| closeIconId | string | | id attribute for the close icon button. |
| closeIcon | React.ReactNode | | Custom icon to render (svg, img, etc...). |
| focusTrapped | boolean | true | When the modal is open, trap focus within it. |
| initialFocusRef | React.RefElement<HTMLElement> | undefined | Sets focus on this specific element when modal opens if focus trap is used. |
| container | Element | | You can specify a container prop which should be of type Element. The portal will be rendered inside that element. The default behavior will create a div node and render it at the at the end of document.body. |
| classNames | {root?: string;overlay?: string;overlayAnimationIn?: string;overlayAnimationOut?: string;modal?: string;modalAnimationIn?: string;modalAnimationOut?: string;closeButton?: string;closeIcon?: string;} | | An object containing classNames to style the modal. |
| styles | {root?: React.CSSProperties;overlay?: React.CSSProperties;overlay?: React.CSSProperties;modalContainer?: React.CSSProperties;modal?: React.CSSProperties;closeButton?: React.CSSProperties;closeIcon?: React.CSSProperties;} | | An object containing the styles objects to style the modal. |
| animationDuration | number | 300 | Animation duration in milliseconds. |
| role | string | "dialog" | ARIA role for modal |
| ref | React.RefElement<HTMLDivElement> | undefined | Ref for modal dialog element |
| ariaLabel | string | | ARIA label for modal |
| ariaLabelledby | string | | ARIA labelledby for modal |
| ariaDescribedby | string | | ARIA description for modal |
| dir | "ltr" \| "rtl" \| "auto" | | Text direction of the modal content. |
| containerId | string | | id attribute for modal container |
| modalId | string | | id attribute for modal | |
| onClose* | () => void | | Callback fired when the Modal is requested to be closed by a click on the overlay or when user press esc key. |
| onEscKeyDown* | (event: KeyboardEvent) => void | | Callback fired when the escape key is pressed. |
| onOverlayClick* | (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void | | Callback fired when the overlay is clicked. |
| onAnimationEnd* | (event: React.AnimationEvent<HTMLDivElement>) => void | | Callback fired when the Modal has exited and the animation is finished. |
License
react-responsive-modal is licensed under the MIT license.