4 min read

Render modal on a route with the parent in background with Tanstack Router

react
tanstack-router
modal
outlet

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.

bash
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.

text
routes/
โ”œโ”€โ”€ __root.tsx
โ”œโ”€โ”€ about.tsx
โ”œโ”€โ”€ index.tsx
โ”œโ”€โ”€ photos_$id.tsx
โ”œโ”€โ”€ photos.{$id}_modal.tsx
โ””โ”€โ”€ photos.tsx

Code 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.

text
src/
โ”œโ”€โ”€ routes/
โ”‚   โ”œโ”€โ”€ __root.tsx
โ”‚   โ”œโ”€โ”€ about.tsx
โ”‚   โ”œโ”€โ”€ index.tsx
โ”‚   โ”œโ”€โ”€ photos-id.tsx
โ”‚   โ”œโ”€โ”€ photos-modal.tsx
โ”‚   โ””โ”€โ”€ photos.tsx
โ””โ”€โ”€ main.tsx

This is how the main file should look like.

main.tsx
// 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
// __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! ๐Ÿš€