I have what I would guess is a pretty common setup in NextJS 13/14 app router. I have a layout.tsx
file which shows my sticky navbar at the top of the screen for navigation purposes:
import "./globals.css";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<div className="relative">
<!-- My navbar is here -->
<div className="sticky top-0 z-50 h-16 w-full bg-blue-200" />;
<div className="px-4">
<div className="flex-grow p-6 md:overflow-y-auto ">{children}</div>
</div>
</div>
</body>
</html>
);
}
And then I create my page.tsx
file, which has a modal in it:
import Modal from "./Modal";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-4">
<div className="mx-auto">
<p>Hello world!</p>
<div className="w-full h-full">
<Modal />
</div>
</div>
</main>
);
}
Here is the Modal code:
"use client";
import { Dialog, Transition } from "@headlessui/react";
import { Fragment, useState } from "react";
export default function Modal() {
const [isOpen, setIsOpen] = useState(false);
function toggleOpen() {
setIsOpen(!isOpen);
}
return (
<>
<button
className=" border-2 rounded-lg border-gray-400"
onClick={toggleOpen}
>
Open me!
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={toggleOpen}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black/25" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Dialog.Panel className="text-center w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className={`text-black text-lg font-medium leading-6`}
>
{"This is a header"}
</Dialog.Title>
<div className="flex mt-4 text-center w-full justify-center">
<div className="ml-3">
<button className="rounded-md" onClick={toggleOpen}>
Close modal
</button>
</div>
</div>
</Dialog.Panel>
</div>
</div>
</Dialog>
</Transition>
</>
);
}
Which gives us something like this:
And when I open the modal, something uglier:
My issue is that when I open the dialog, the modal transition does NOT obscure layout.tsx
(e.g. the blue header), but it obscures the rest of the page not defined by the layout component (e.g. the children of the layout). I know this is because of the way that Nextjs nests components:
So I suppose my question is, isn't the fact that any full-screen overlay doesn't go over top of the code in layout.tsx
a serious shortcoming of using layout.tsx
for any sort of navigation/permanent fixture purposes? If the layout will always sit on top of any modal overlay, won't that always just be pretty ugly, no matter what?
I think this can just be solved with css as opposed to needing to do anything funky with next's project structure. It looks like the sticky header has a z-index of 50, so your transition component needs to have a higher z-index to show up on top.
Additionally, the sticky header's width issue is probably being caused by a scrollbar appearing/disappearing after disabling scrolling when the modal is opened up? In that case you can just set the header's width to 100vw
instead of 100%
.