
Render modal on a route with the parent in background with Tanstack Router
Modals are one of the most versatile UI patterns in modern web development. They allow us to present focused content without losing context of the underlying page. And it also helps us to improve the user experience and SEO.
Today, I'll walk you through creating modal routes using Tanstack Router - a powerful, type-safe routing solution for React applications. We'll build a gallery where clicking on images opens them in a modal while keeping the gallery visible in the background.
Why Modal Routes Matter ๐ฏ
Modal routes are perfect for scenarios where you want to:
- Show detailed views: Display item details, user profiles, or product information without losing the list context
- Handle forms: Present forms for editing, creating, or configuring while keeping the parent page accessible
- Improve UX: Maintain user context and reduce navigation friction
- Deep linking: Allow users to share direct links to modal content
- SEO: Better for SEO as the modal is a separate route
You can do the same thing with react-router-dom and Next.js. you can checkout those articles later.
Prerequisites ๐
Before we dive in, make sure you have:
- A basic understanding of React and TypeScript
- Familiarity with Tanstack Router concepts (if not, check their excellent documentation)
- A React project set up with TypeScript support
We'll be building a product photo gallery application where users can browse images and click to view them in a modal overlay.
Install Tanstack Router
First, let's set up Tanstack Router in our project. We'll use the createRouter function to define our routes.
npm install @tanstack/react-router
# or
pnpm add @tanstack/react-router
# or
yarn add @tanstack/react-routerโ๏ธ Important
Since TanStack Router supports two styles of routing โ file-based and code-based โ I'll provide GitHub repo links instead of dumping every file here. Explore them at your own pace.
File Based Routing
In file based routing we can create a folder and inside that folder we can create a file for each route or we can use the . for child routes like in the example below.
The whole code is available in the github repo.
routes/
โโโ __root.tsx
โโโ about.tsx
โโโ index.tsx
โโโ photos_$id.tsx
โโโ photos.{$id}_modal.tsx
โโโ photos.tsxCode Based Routing
On the other hand we can use the code based routing to create the routes. Code based routing works similar to react-router-dom.
The whole code is available in the github repo.
src/
โโโ routes/
โ โโโ __root.tsx
โ โโโ about.tsx
โ โโโ index.tsx
โ โโโ photos-id.tsx
โ โโโ photos-modal.tsx
โ โโโ photos.tsx
โโโ main.tsxThis is how the main file should look like.
// main.tsx
import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import { createRouteMask, createRouter, RouterProvider } from '@tanstack/react-router'
import { rootRoute } from './routes/__root'
import { aboutRoute } from './routes/about'
import { indexRoute } from './routes/index'
import { photosRoute } from './routes/photos'
import { photoChildRoute } from './routes/photos-id'
import { photoModalRoute } from './routes/photos-modal'
const routeTree = rootRoute.addChildren([
indexRoute,
aboutRoute,
photoChildRoute,
photosRoute.addChildren([photoModalRoute]),
])
const photoModalToPhotoMask = createRouteMask({
routeTree,
from: '/photos/$id/modal',
to: '/photos/$id',
params: true,
})
const router = createRouter({
routeTree,
routeMasks: [photoModalToPhotoMask],
scrollRestoration: true,
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
const rootElement = document.getElementById('root')!
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
)
}This is how the root file looks like.
// __root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router'
import Navbar from '../_components/navbar'
type PhotoModal = {
id: 'photo'
photoId: string
}
type ModalObject = PhotoModal
export const rootRoute = createRootRoute({
validateSearch: (search) =>
search as {
modal?: ModalObject
},
component: RootComponent,
})
function RootComponent() {
return (
<>
<Navbar />
<Outlet />
</>
)
}What We've Built ๐
Congratulations! You've successfully implemented a modal route system using Tanstack Router. Here's what we accomplished:
- Type-safe routing: All routes and parameters are fully typed
- Nested routing: The modal renders as a child route while preserving the parent
- Deep linking: Modal URLs are shareable and bookmarkable
- Smooth UX: Users can navigate back to the gallery without losing context
Key Benefits of Tanstack Router
- File-based routing: Routes are defined in separate files for better organization
- Type safety: Full TypeScript support with automatic type inference
- Modern patterns: Built for React 18+ with concurrent features
- Developer experience: Excellent tooling and debugging capabilities
Conclusion
I hope you find this article helpful.
You can checkout the github repo for the code.
Happy coding! ๐