Modal component with Next.js – pages router

In this tutorial, I will show you how to create a basic Modal component from scratch in a Next.js application with pages router.

Before we start

Important: Next v13 with pages router was used for this tutorial . Here is the Github repository with the source code.
If you are using an older version of Next.js you can refer to old codebase where Next v10 was used.

First, we need to initiate a new Next.js project using npx or yarn.

npx create-next-app
# or
yarn create next-app

The main purpose of every modal is to appear on top of every other element on the page. For this reason, it’s a good practice to place it in a separated DOM node, outside the one that holds the rest of the UI. However, in a classical React SPA, the app renders inside a specified DOM node, so how do we In such scenarios, React’s API offers a feature called Portal. It allows components to render in a specific DOM node outside the root one that the application uses. It’s pretty easy to achieve in a SPA because we can modify the main index.html that holds all the rest of the app. However, Next.js application is very different from a SPA, so we don’t have such a file at our disposal. The way to modify the root DOM structure of our app is to use the _document.js file, which is provided by the library. You can read more about it in the official documentation. Please create this file inside the pages folder and add the following content:

import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html lang="en">
      <Head />
        <Main />
        <NextScript />
        {/* Below we insert the modal wrapper*/}
        <div id="modal-root"></div>

Basically, we used the default code in the component and inserted a div element where our Modal will be mounted.

Create the Modal component

Let’s create components/Modal.js and add this code to it.

import React from "react";
import ReactDOM from "react-dom";

const Modal = ({ onClose, children, title }) => {
    const handleCloseClick = (e) => {

    const modalContent = (
        <div className="modal-overlay">
            {/* Wrap the whole Modal inside the newly created StyledModalWrapper
            and use the ref */}
            <div className="modal-wrapper">
                <div className="modal">
                    <div className="modal-header">
                        <a href="#" onClick={handleCloseClick}>
                    {title && <h1>{title}</h1>}
                    <div className="modal-body">{children}</div>

    return ReactDOM.createPortal(

export default Modal

And here is the basic styling for the Modal, which I placed in styles/globals.css which we will import to pages/_app.js .

.modal-wrapper {
  width: 500px;
  height: 600px;

.modal {
  background: white;
  border-radius: 15px;
  padding: 15px;

.modal-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(0, 0, 0, 0.5);

.modal-body {
  padding-top: 10px;

.modal-header {
  display: flex;
  justify-content: flex-end;
  font-size: 25px;

There is no need to explain every line of CSS here, the key thing to remember is to have an overlay(.modal-overlay), which should take up the whole screen and have absolute positioning. Inside the overlay element, we center the Modal element.

Here is how I implemented the newly created Modal component in pages/index.js:

import Modal from "./components/Modal";
import {useState} from "react";

export default function Home() {
  const [showModal, setShowModal] = useState(false);

  return (
        <button onClick={() => setShowModal(true)}>Open Modal</button>
        {showModal &&
            <Modal onClose={() => setShowModal(false)}>
                Hello from the modal!

Let’s test it now:

Simple modal with React and Next.js

Tadaa! We have a simple modal implemented in Next.js 13.

What next?

Make sure to look at the 2nd part of this tutorial – I have enhanced the modal and allow it to be closed when the user clicks outside of it(backdrop click).

I hope you enjoyed this tutorial. Check out my blog for more dev recipes.