How to Create Modals in React with @headlessui/react

How to Create Modals in React with @headlessui/react

Featured on Hashnode

Have you ever found it difficult to create reusable and accessible dialog or modal components from scratch? Headless UI is a popular library that can help. In front-end development, modals or dialogs are essential components that provide information or prompt users for input.

In this article, we’ll explore how to leverage Headless UI in React to build robust and accessible modals in React.

Why Use @headlessui/react for Dialogs?

Dialogs, or modals, offer several benefits in user interactions, whether for confirmations, alerts, or forms. Here’s why @headlessui/react is an excellent choice for creating dialogs:

  1. Accessibility: Accessibility is vital in front-end development. @headlessui/react ensures your dialogs are accessible to all users, incorporating necessary features like focus management and ARIA attributes.

  2. Customizability: With no predefined styles, you have complete control over the design and layout of your dialogs. You can effortlessly add animations, transitions, and custom functionality to enhance the user experience.

  3. Reusability: Headless UI components are designed to be highly reusable, reducing code duplication and making maintenance easier.

  4. Ease of Use: The library integrates smoothly with other React libraries and frameworks, making it easy to use in both small and large projects.

Setting Up the Environment

To create a modal in React using @headlessui/react, we must first set up our development environment. This involves ensuring you have the necessary tools and libraries installed.

  1. Node.js: Required for running JavaScript on the server side. Download and install Node.js from Nodejs if you haven't already.

  2. Package Manager: You'll need a package manager to manage dependencies and installations. We'll use npm (Node Package Manager) in this article, but you can also use yarn if you prefer.

  3. React Setup: We assume you have a basic understanding of React and a project setup. If not, create a new React app using npx create-react-app my-app to get started with a basic project structure.

Installing @headlessui/react

After setting up the environment, use your command terminal to run the following command to install @headlessui/react

npm install @headlessui/react

or if you prefer using Yarn

yarn add @headlessui/react

Creating a Basic Dialog Component

Now that we have @headlessui/react installed, let's create a basic dialog component. This will cover the fundamental structure.

const BasicDialog = ({ isOpen, onClose, title, children }) => {
  return (
    <Transition.Root show={isOpen} as={Fragment}>
      <Dialog as="div" onClose={onClose}>
        <div className="fixed inset-0 z-10 overflow-y-auto">
          <div className="flex items-center justify-center min-h-screen px-4 text-center">
            <Transition.Child
              as={Fragment}
              enter="ease-out duration-300"
              enterFrom="opacity-0"
              enterTo="opacity-100"
              leave="ease-in duration-200"
              leaveFrom="opacity-100"
              leaveTo="opacity-0"
            >
              <Dialog.Panel className="fixed inset-0 transform bg-gray-500 bg-opacity-75 flex items-center justify-center">
                <div className="bg-white rounded-lg p-8 max-w-md w-full">
                  <Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
                    {title}
                  </Dialog.Title>
                  <div className="mt-2">
                    <p className="text-sm text-gray-500">{children}</p>
                  </div>
                  <div className="mt-4">
                    <button
                      type="button"
                      className="inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
                      onClick={onClose}
                    >
                      Close
                    </button>
                  </div>
                </div>
              </Dialog.Panel>
            </Transition.Child>
          </div>
        </div>
      </Dialog>
    </Transition.Root>
  );
};

The component's breakdown is as follows:

  1. BasicDialog is a functional component that receives four props:

    • isOpen: a boolean indicating whether the dialog is open or closed

    • onClose: a function to call when the dialog is closed

    • title: the title of the dialog

    • children: the content of the dialog

  2. The component returns a Transition.Root component, which manages the transition effects. Inside Transition.Root, there are two main elements:

    • Dialog: the dialog container, which renders an overlay and a panel

    • Transition.Child: the animated content of the dialog, which includes the title, children, and close button

  3. The Dialog.Panel component renders the dialog's content, including:

    • Dialog.Title: the title of the dialog

    • div: the children content

    • button: the close button

Example Code for a Simple Modal

Let's see how we can integrate this component into our React application.

import { useState } from 'react';
import BasicDialog from '../components/Modals/BasicDialog';

export default function ModalTest () {
  const [isOpen, setIsOpen] = useState(false);

  const openDialog = () => {
    setIsOpen(true);
  };

  const closeDialog = () => {
    setIsOpen(false);
  };

  return (
     <div className="text-center mt-4>
      <button onClick={openDialog}>Open Dialog</button>
      <BasicDialog isOpen={isOpen} onClose={closeDialog} title="Example Dialog">
        This is a simple example of a dialog component using `@headlessui/react`.
      </BasicDialog>
    </div>
  );
};

This example creates a button that opens a reusable dialog component BasicDialog imported from a separate file when clicked, and the dialog can be closed by clicking the close button, with its open/closed state managed using React's useState hook.

A diagram showing Headless UI dialog example

In the image above, you can see the text Open Dialog displayed on the screen, which, when clicked, opens the dialog component.

A diagram showing the dialog when clicked

When the Open Dialog text is clicked, it immediately opens the modal window, which renders the details in the Basic Dialog component, as previously created.

Handling Dialog State

As we explore the dialog component, you may wonder how its state is managed. Specifically, how does the dialog toggle between open and closed states? To achieve this, we utilize React's useState hook in the parent component to manage the dialog's state.

We create two state variables:

  • isOpen: a boolean to track whether the dialog is open or closed

  • setIsOpen: a function to update the isOpen state.

We also define two handlers:

  • openDialog: sets isOpen to true

  • closeDialog: sets isOpen to false

Passing State and Handlers to the Dialog Component

Next, we pass the isOpen state,title and closeDialog handler as props to the BasicDialog component, connecting the state and behavior to the dialog.

<BasicDialog isOpen={isOpen} onClose={closeDialog} title="Example Dialog">
        This is a simple example of a dialog component using `@headlessui/react`.
      </BasicDialog>

Conclusion

In this article, we covered how to create a basic modal component using @headlessui/react. We started by discussing the importance of modals in front-end development and the benefits of using Headless UI for building accessible, customizable, and reusable components. We then walked through the setup process and the creation of a simple dialog component. Finally, we integrated the BasicDialog component into a React application with an example to demonstrate its usage.