Modals let you show extra info without leaving the page. URL query parameters help open modals while keeping the main page visible.
Modal pattern with intercepting routes in NextJS
import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import { useState, useEffect } from 'react'; export default function Page() { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const [modalOpen, setModalOpen] = useState(false); useEffect(() => { const modalParam = searchParams.get('modal'); setModalOpen(modalParam === 'true'); }, [searchParams]); function openModal() { router.push(`${pathname}?modal=true`, { scroll: false }); } function closeModal() { router.push(pathname, { scroll: false }); } return ( <> <main> <h1>Main Page Content</h1> <button onClick={openModal}>Open Modal</button> </main> {modalOpen && ( <div role="dialog" aria-modal="true" aria-labelledby="modal-title" tabIndex={-1} className="modal"> <h2 id="modal-title">Modal Content</h2> <p>This is a modal opened via URL query parameter.</p> <button onClick={closeModal}>Close Modal</button> </div> )} </> ); }
The modal state is controlled by a URL query parameter (e.g., ?modal=true).
Using router.push with scroll: false keeps the page position stable.
router.push(`${pathname}?modal=true`, { scroll: false });
router.push(pathname, { scroll: false });const modalParam = searchParams.get('modal'); setModalOpen(modalParam === 'true');
This Next.js component shows a main page with a button. Clicking the button adds ?modal=true to the URL and opens a modal. Closing the modal removes the query parameter. The modal uses ARIA roles for accessibility and a semi-transparent background overlay.
import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import { useState, useEffect } from 'react'; export default function ModalPage() { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const [modalOpen, setModalOpen] = useState(false); useEffect(() => { const modalParam = searchParams.get('modal'); setModalOpen(modalParam === 'true'); }, [searchParams]); function openModal() { router.push(`${pathname}?modal=true`, { scroll: false }); } function closeModal() { router.push(pathname, { scroll: false }); } return ( <> <main> <h1>Welcome to the Main Page</h1> <button onClick={openModal} aria-haspopup="dialog" aria-expanded={modalOpen}>Open Modal</button> </main> {modalOpen && ( <div role="dialog" aria-modal="true" aria-labelledby="modal-title" tabIndex={-1} className="modal" style={{ position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: 'white', padding: '1rem', boxShadow: '0 4px 6px rgba(0,0,0,0.1)', zIndex: 1000 }}> <h2 id="modal-title">Modal Window</h2> <p>This modal opened using URL query parameters keeps the main page visible.</p> <button onClick={closeModal}>Close Modal</button> </div> )} {modalOpen && <div aria-hidden="true" style={{ position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh', backgroundColor: 'rgba(0,0,0,0.3)', zIndex: 999 }} />} </> ); }
Always use ARIA roles like role="dialog" and aria-modal="true" for accessibility.
Keep the background page visible but inert by using an overlay with aria-hidden="true".
Using the Next.js router with query parameters lets you change the URL without full page reload, improving user experience.
Modals with URL query parameters show popups without leaving the page.
Use URL query parameters to control modal open/close state.
Ensure accessibility with proper ARIA roles and keyboard focus management.