- read

Mastering Modals in Next.js: A Comprehensive Guide

Daniel Craciun 27

Mastering Modals in Next.js: A Comprehensive Guide

Daniel Craciun
Level Up Coding
Published in
5 min read7 hours ago

--

Modals are an essential component in web development, offering a sleek way to interact with users without disrupting the flow of your web application.

When it comes to Next.js 13, a powerful and popular framework, integrating modals seamlessly can make a significant difference in user experience.

In this article, we’ll dive deep into the world of modals in Next.js 13, exploring various techniques, best practices, and real-world examples.

So, let’s dig into the world of Next.js 13 modals!

Table of Contents

  • Setup Next.js 13 Project Environment
  • Dependencies Installation
  • What is a Modal?
  • Creating a Modal UI Component
  • Rendering the Modal

Setup Next.js 13 Project Environment

To setup a Next.js project, please check out the instructions here. The installation process generally consists of running the following command in the terminal.

npx create-next-app@latest

Dependencies Installation

In this tutorial I use the Shadcn-UI library to make the modal component look nice for illustrational purposes. This step is optional but highly recommended so you don’t get lost while substituting code.

Please see the article below to setup Shadcn-UI.

What is a Modal?

Before we get coding, it’s important to understand what a modal is, and how it works.

A modal is a UI window that sits on top of the main page, often blurring the background of the main page.

In the context of Next.js, this would be a new page.tsx that sits on top of the main page.tsx, blurring out the contents of the main page.tsx.

This feature is particularly useful if you want to show the user content from another route while keeping them on the same page.

An example of this is when you click on an image, then the image pops up over the main page without redirecting you to another page just to display the image; this is also known as route interception.

Creating a Modal UI Component

We will start with the easy bit, we will construct a component that will be displayed after the route interception occurs.

Create a new file called Modal.tsx inside a components/ folder inside the app/ directory; copy the following code into the file:

"use client"

import { MouseEventHandler, useCallback, useEffect, useRef } from "react"
import { useRouter } from "next/navigation"

export default function Modal({ children }: { children: React.ReactNode }) {
const overlay = useRef(null)
const wrapper = useRef(null)
const router = useRouter()

const onDismiss = useCallback(() => {
router.back()
}, [router])

const onClick: MouseEventHandler = useCallback(
(e) => {
if (e.target === overlay.current || e.target === wrapper.current) {
if (onDismiss) onDismiss()
}
},
[onDismiss, overlay, wrapper]
)

const onKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === "Escape") onDismiss()
},
[onDismiss]
)

useEffect(() => {
document.addEventListener("keydown", onKeyDown)
return () => document.removeEventListener("keydown", onKeyDown)
}, [onKeyDown])

return (
<div
ref={overlay}
className="fixed bottom-0 left-0 right-0 top-0 z-10 mx-auto bg-black/60"
onClick={onClick}
>
<div
ref={wrapper}
className="absolute left-1/2 top-1/2 w-full -translate-x-1/2 -translate-y-1/2 p-6 sm:w-10/12 md:w-8/12 lg:w-1/2"
>
{children}
</div>
</div>
)
}
  1. The onDismiss callback ensures that the user is redirected (can be interpreted as the modal disappearing) to the previous page when exiting the modal.
  2. onClick ensures the modal can be removed by clicking outside the modal area; onKeyDown ensures that the modal can be closed at any time using esc.
  3. Finally, the useEffect hook will listen for keydown events from the user and perform the above logic.

Rendering the Modal

Now that we have the modal component setup, it’s ready to be rendered when the user presses a certain UI element.

Inside app/page.tsx, copy the following code:

import Link from "next/link"

export default async function page() {
return (
<main className="flex grow">
<Link className="h-fit text-blue-500" href="/intercept">
Go to intercept
</Link>
</main>
)
}
  • This code renders a link to a route intercept, which will be intercepted by a modal.

Now create a new folder called intercept inside the app directory with a page.tsx file, and copy the following code:

import { FC } from "react"

import { Card, CardContent } from "@/components/ui/card"

interface pageProps {}

const page: FC<pageProps> = ({}) => {
return (
<Card className="h-[600px]">
<CardContent className="flex h-full items-center justify-center">
This is a not a modal
</CardContent>
</Card>
)
}

export default page
  • As you can see, this is a simple page that renders a card component (using Shadcn-UI) which tells you this is a full page, not a modal; you can access this page directly by typing localhost:3000/intercept.

Now to implement the route interception logic!

Create the following structure in your Next.js folder structure:

app/@modal/(.)intercept/page.tsx.

  • The @modal tells Next.js that this route is a modal route, and should be rendered inside the common layout.
  • (.)intercept means find the page located at ‘.’ (which is the current folder hierarchy level) and intercept the original navigation (to /intercept) and render this page.tsx instead.

Inside page.tsx, copy the following code:

 import { FC } from "react"

import { Card, CardContent } from "@/components/ui/card"
import Modal from "@/components/Modal"

interface pageProps {}

const page: FC<pageProps> = ({}) => {
return (
<Modal>
<Card className="h-[600px]">
<CardContent className="flex h-full items-center justify-center">
This is a modal
</CardContent>
</Card>
</Modal>
)
}

export default page
  • Note that this page actually renders the Modal.tsx component created earlier.

Also, create a file called default.tsx within the app/@modal directory alongside (.)intercept with the following code:

export default function Default() {
return null
}
  • This is a necessary step as of Next.js 13.4.19, later versions may not require this.

Next, inside the root layout.tsx file, copy the following code:

import "@/styles/globals.css"

import { Provider } from "@/components/providers"


export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode
modal: React.ReactNode
}) {
return (
<html lang="en" className={inter.className}>
<body className="flex min-h-screen flex-col">
<Provider attribute="class" defaultTheme="dark" enableSystem>
{children}
{modal}
</Provider>
</body>
</html>
)
}
  • This code ensures that the modal is rendereable in the main layout alongside any children pages.

Conclusion

Here is an image showing the modal pop up when pressing the link to the intercept page:

A modal showing up when pressing the ‘go to intercept’ button.
A modal showing up when pressing the ‘go to intercept’ button.

If you enjoyed this article, check out my profile for many more stories like this, and stay tuned for future articles! 👍

Connect With Me 🌐