DEV Community

Oliver Reed
Oliver Reed

Posted on

Getting Started with React Modal: Building Your First Modal Dialog

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
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add react-modal
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add react-modal
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "react-modal": "^3.16.1",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 useState to control modal visibility
  • onRequestClose: Called when user clicks overlay or presses Escape
  • contentLabel: Required for screen readers
  • Styling: Can be customized with style and className props

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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

  1. Modal not displaying: Ensure isOpen is set to true and you've called Modal.setAppElement('#root') in your app initialization.

  2. Accessibility warnings: Always provide a contentLabel prop. This is required for screen readers and will show a warning in development if missing.

  3. Modal not closing: Make sure onRequestClose is provided and properly updates the state. The modal closes when clicking the overlay or pressing Escape.

  4. Styling issues: Use the style prop with content and overlay properties to customize appearance. For centered modals, use transform: 'translate(-50%, -50%)' with top and left at 50%.

  5. Body scroll lock: React Modal automatically prevents body scrolling when open. If you need to disable this, use the shouldCloseOnOverlayClick and shouldCloseOnEsc props.

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)