Mastering Modals in Next.js: A Comprehensive Guide
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>
)
}
- The
onDismiss
callback ensures that the user is redirected (can be interpreted as the modal disappearing) to the previous page when exiting the modal. onClick
ensures the modal can be removed by clicking outside the modal area;onKeyDown
ensures that the modal can be closed at any time usingesc
.- Finally, the
useEffect
hook will listen forkeydown
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 thispage.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 anychildren
pages.
Conclusion
Here is an image showing the modal pop up when pressing the link to the intercept
page:
If you enjoyed this article, check out my profile for many more stories like this, and stay tuned for future articles! 👍