Imperative modal stack manager for React. Open modals imperatively with pushModal(); they stack on screen with an overlay, animations, and focus trap.
npm install @svnrnns/react-modals- Mount
ModalRootonce in your app (e.g. in your root layout).
import { ModalRoot } from "@svnrnns/react-modals";
import "@svnrnns/react-modals/styles.css";
export default function RootLayout({ children }) {
return (
<html>
<body>
<ModalRoot />
{children}
</body>
</html>
);
}ModalRoot props:
- disableBodyScroll – When
true, appliesoverflow: hiddentodocument.bodywhile any modal is open, hiding the body scrollbar. Default:false(body scroll is not disabled unless you set this).
- Import and use
pushModal,popModal,closeModalById, andcloseAllModalsanywhere (no context needed).
import { pushModal } from "@svnrnns/react-modals";
function MyContent({
name,
closeModal,
}: {
name: string;
closeModal: () => void;
}) {
return (
<div>
<p>Hello, {name}</p>
<button onClick={closeModal}>Close</button>
</div>
);
}
function App() {
return (
<button
onClick={() =>
pushModal({
component: MyContent,
props: { name: "World" },
title: "Greeting",
})
}
>
Open modal
</button>
);
}TypeScript infers props from your component, so props: { name: "World" } is type-checked.
- component – React component to render (receives
props+closeModal). - props – Props for the component (inferred from
component). - title – Optional title in the modal header.
- hideHeader – If
true, the header (title + close button) is hidden; close via Escape, click outside, or a button in the content. - width / height – Optional modal size (e.g.
"400px",400). - className – Optional class for the modal wrapper.
- footer – Optional
{ component, props?, className? }for a footer component. The footer component receives its props plus closeModal (same as the modal content), so you can add close/cancel buttons in the footer. - onClose – Callback when the modal is closed.
- disableClickOutside – If
true, clicking the backdrop does not close. - disableEsc – If
true, Escape does not close. - disableAutoFocus – If
true, no element is focused when the modal opens (focus trap for Tab still works).
- pushModal(options) – Pushes a modal onto the stack. Returns the modal id (string). Store it to close that modal later with
closeModalById(id). - popModal() – Closes the topmost modal.
- closeModalById(id) – Closes the modal with the given id (the one returned by
pushModal). - closeAllModals() – Closes all modals.
Each modal content component receives closeModal (no arguments): call it to close that modal (works even if it is not the top one).
When a modal is on top, focus is trapped inside it: Tab / Shift+Tab wrap within the modal, and when the modal closes, focus returns to the previously focused element.
Override these in your app to style modals:
| Variable | Default | Description |
|---|---|---|
--modal-bg |
#fff |
Modal background |
--modal-border |
1px solid transparent |
Modal border (width, style, color; e.g. 2px solid #e2e8f0) |
--modal-max-width |
min(90vw, 90dvw) |
Maximum modal width |
--modal-max-height |
min(95vh, 95dvh) |
Maximum modal height |
--modal-padding |
1rem |
Padding for header and content area |
--modal-footer-padding |
var(--modal-padding) |
Padding for the footer |
--modal-gap |
1rem |
Gap between header, content, footer |
--modal-title-color |
#0f172a |
Title text color |
--modal-title-font-size |
1.25rem |
Title font size |
--modal-title-line-height |
1 |
Title line height |
--modal-border-radius |
0.5rem |
Modal corners |
--modal-shadow |
0 25px 50px -12px rgb(0 0 0 / 0.25) |
Box shadow |
--modal-overlay-bg |
rgba(0, 0, 0, 0.3) |
Backdrop color |
--modal-overlay-blur-filter |
blur(4px) |
Backdrop blur (full filter value) |
--modal-dim-filter |
brightness(0.85) |
Filter for modals behind the top one |
--modal-duration |
150ms |
Animation duration |
--modal-close-size |
2rem |
Close button width and height |
--modal-close-padding |
0.25rem |
Close button padding |
--modal-close-border-radius |
0.25rem |
Close button border radius |
--modal-close-bg |
transparent |
Close button background |
--modal-close-hover-bg |
rgba(0, 0, 0, 0.05) |
Close button hover background |
--modal-close-color |
currentColor |
Close icon color |
--modal-close-hover-color |
currentColor |
Close icon hover color |
Example:
:root {
--modal-border: 1px solid #e2e8f0;
--modal-border-radius: 0.75rem;
--modal-overlay-blur-filter: blur(8px);
}- React 18 or 19
Modals are rendered with createPortal into document.body, so they work with Next.js App Router and SSR. This also applies to other React frameworks.