React Modal is a simple, accessible modal component library for React that provides essential modal functionality with focus management and accessibility features. It's designed to be easy to use while offering customization options for building modal dialogs in your applications. This guide walks through setting up and creating your first modal dialog using React Modal with React, from installation to a working implementation. This is part 28 of a series on using React Modal with React.
Prerequisites
Before you begin, make sure you have:
- Node.js version 14.0 or higher installed
- npm, yarn, or pnpm package manager
- A React project (version 16.8 or higher) or create-react-app setup
- Basic knowledge of React hooks (useState)
- Familiarity with JavaScript/TypeScript
Installation
Install React Modal using your preferred package manager:
npm install react-modal
Or with yarn:
yarn add react-modal
Or with pnpm:
pnpm add react-modal
After installation, your package.json should include:
{
"dependencies": {
"react-modal": "^3.16.1",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
Project Setup
React Modal requires minimal setup. You may want to set the app element for accessibility. Add this to your main entry file:
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import Modal from 'react-modal';
import App from './App';
// Set the app element for accessibility
Modal.setAppElement('#root');
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
First Example / Basic Usage
Let's create a simple modal component. Create a new file src/SimpleModal.jsx:
// src/SimpleModal.jsx
import React, { useState } from 'react';
import Modal from 'react-modal';
function SimpleModal() {
const [modalIsOpen, setIsOpen] = useState(false);
function openModal() {
setIsOpen(true);
}
function closeModal() {
setIsOpen(false);
}
return (
<div style={{ padding: '20px' }}>
<button onClick={openModal}>Open Modal</button>
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
contentLabel="Example Modal"
>
<h2>Hello, I am a modal!</h2>
<p>This is the modal content.</p>
<button onClick={closeModal}>Close</button>
</Modal>
</div>
);
}
export default SimpleModal;
Now, update your App.jsx to use the modal:
// src/App.jsx
import React from 'react';
import SimpleModal from './SimpleModal';
import './App.css';
function App() {
return (
<div className="App">
<h1>React Modal Example</h1>
<SimpleModal />
</div>
);
}
export default App;
This creates a basic modal that opens when you click the button and closes when you click the close button or press Escape.
Understanding the Basics
React Modal uses a simple API where:
- isOpen: Boolean that controls whether the modal is visible
- onRequestClose: Callback function called when the modal should close
- contentLabel: Accessible label for the modal (required for accessibility)
- children: Content to display inside the modal
Key concepts:
-
State Management: Use
useStateto control modal visibility - onRequestClose: Called when user clicks overlay or presses Escape
- contentLabel: Required for screen readers
-
Styling: Can be customized with
styleandclassNameprops
Here's an example with custom styling:
// src/StyledModal.jsx
import React, { useState } from 'react';
import Modal from 'react-modal';
function StyledModal() {
const [modalIsOpen, setIsOpen] = useState(false);
const customStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
maxWidth: '500px',
width: '90%',
padding: '20px',
borderRadius: '8px',
border: 'none',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
},
overlay: {
backgroundColor: 'rgba(0, 0, 0, 0.5)'
}
};
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Styled Modal</button>
<Modal
isOpen={modalIsOpen}
onRequestClose={() => setIsOpen(false)}
style={customStyles}
contentLabel="Styled Modal Example"
>
<h2 style={{ marginTop: 0 }}>Styled Modal</h2>
<p>This modal has custom styling.</p>
<button onClick={() => setIsOpen(false)}>Close</button>
</Modal>
</div>
);
}
export default StyledModal;
Practical Example / Building Something Real
Let's build a contact form modal with validation:
// src/ContactModal.jsx
import React, { useState } from 'react';
import Modal from 'react-modal';
function ContactModal() {
const [modalIsOpen, setIsOpen] = useState(false);
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const [errors, setErrors] = useState({});
const customStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
maxWidth: '500px',
width: '90%',
padding: '24px',
borderRadius: '8px',
border: 'none',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
},
overlay: {
backgroundColor: 'rgba(0, 0, 0, 0.5)'
}
};
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }));
}
};
const validateForm = () => {
const newErrors = {};
if (!formData.name.trim()) {
newErrors.name = 'Name is required';
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Invalid email format';
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validateForm()) {
console.log('Form submitted:', formData);
setIsOpen(false);
setFormData({ name: '', email: '', message: '' });
setErrors({});
}
};
const handleClose = () => {
setIsOpen(false);
setFormData({ name: '', email: '', message: '' });
setErrors({});
};
return (
<div style={{ padding: '20px' }}>
<button
onClick={() => setIsOpen(true)}
style={{
padding: '10px 20px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Contact Us
</button>
<Modal
isOpen={modalIsOpen}
onRequestClose={handleClose}
style={customStyles}
contentLabel="Contact Form Modal"
>
<h2 style={{ marginTop: 0 }}>Contact Us</h2>
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="name" style={{ display: 'block', marginBottom: '4px' }}>
Name *
</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleInputChange}
style={{
width: '100%',
padding: '8px',
border: `1px solid ${errors.name ? '#dc3545' : '#ddd'}`,
borderRadius: '4px'
}}
/>
{errors.name && (
<div style={{ color: '#dc3545', fontSize: '14px', marginTop: '4px' }}>
{errors.name}
</div>
)}
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="email" style={{ display: 'block', marginBottom: '4px' }}>
Email *
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleInputChange}
style={{
width: '100%',
padding: '8px',
border: `1px solid ${errors.email ? '#dc3545' : '#ddd'}`,
borderRadius: '4px'
}}
/>
{errors.email && (
<div style={{ color: '#dc3545', fontSize: '14px', marginTop: '4px' }}>
{errors.email}
</div>
)}
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="message" style={{ display: 'block', marginBottom: '4px' }}>
Message *
</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleInputChange}
rows={4}
style={{
width: '100%',
padding: '8px',
border: `1px solid ${errors.message ? '#dc3545' : '#ddd'}`,
borderRadius: '4px',
resize: 'vertical'
}}
/>
{errors.message && (
<div style={{ color: '#dc3545', fontSize: '14px', marginTop: '4px' }}>
{errors.message}
</div>
)}
</div>
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
<button
type="button"
onClick={handleClose}
style={{
padding: '8px 16px',
border: '1px solid #ddd',
backgroundColor: 'white',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Cancel
</button>
<button
type="submit"
style={{
padding: '8px 16px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Submit
</button>
</div>
</form>
</Modal>
</div>
);
}
export default ContactModal;
This example demonstrates:
- Form handling with validation
- Error display and styling
- Custom modal styling
- Proper form submission
- Modal state management
- Accessibility with contentLabel
Common Issues / Troubleshooting
Modal not displaying: Ensure
isOpenis set totrueand you've calledModal.setAppElement('#root')in your app initialization.Accessibility warnings: Always provide a
contentLabelprop. This is required for screen readers and will show a warning in development if missing.Modal not closing: Make sure
onRequestCloseis provided and properly updates the state. The modal closes when clicking the overlay or pressing Escape.Styling issues: Use the
styleprop withcontentandoverlayproperties to customize appearance. For centered modals, use transform: 'translate(-50%, -50%)' with top and left at 50%.Body scroll lock: React Modal automatically prevents body scrolling when open. If you need to disable this, use the
shouldCloseOnOverlayClickandshouldCloseOnEscprops.
Next Steps
Now that you have a basic understanding of React Modal:
- Learn about advanced features like portal rendering and focus management
- Explore custom animations and transitions
- Implement nested modals
- Add accessibility enhancements
- Learn about other modal libraries (react-aria-modal, reach-ui)
- Check the official repository: https://github.com/reactjs/react-modal
- Look for part 29 of this series for more advanced topics
Summary
You've successfully set up React Modal in your React application and created your first modal dialog. The library provides a simple API for building accessible modals with essential features like focus management and keyboard navigation.
SEO Keywords
react-modal
React modal dialog
react-modal tutorial
React modal component
react-modal installation
React accessible modal
react-modal example
React popup modal
react-modal setup
React dialog component
react-modal styling
React modal form
react-modal accessibility
React modal library
react-modal getting started
Top comments (0)