@patch-kit/modal
v1.1.0
Published
Modal Engine for React
Readme
@patch-kit/modal
A Modal Engine built with Zustand and React.
This modal focuses on being a purely technical engine, its only job is to handle the annoying parts of managing modals so the application never has to. The application remains fully in control of every visual decision.
The modal automatically handles stacking, z-indexing, focus-trapping, scroll-lock, keyboard-close, and outside-click close.
Interactive demo: Default
Folder contents
useModal.tsx- Zustand store,useModalhook, and shared modal types.Modal.tsx- Single modal instance renderer (overlay + content).ModalRenderer.tsx- Root renderer that portals the modal stack.index.ts- Public exports.
Exports
useModal- Hook that exposesshowModal,closeModal,closeAllModals.ModalRenderer- Component that renders all active modals todocument.body.
How it works
- Modals are stored in a stack (
ModalInstance[]) in Zustand. showModal(content, options?)pushes a new modal, or replaces an existing one by id.closeModal()closes the top modal;closeModal(id)closes a specific one.ModalRenderermaps the stack toModaland portals it outside the React tree.
Usage
// 1) Render the engine once, near the app root
<ModalRenderer />
// 2) Open/close modals from anywhere
const { showModal, closeModal } = useModal();
showModal(
<YourStyledModal onClose={() => closeModal()} />,
{
closeOnOutsideClick: true, // default: true
id: "optional-custom-id", // default: auto-generated UUID
ariaLabel: "account settings",
ariaDescribedBy: "settings-desc",
}
);The content passed to showModal is rendered inside the engine's dialog wrapper.
ModalRenderer props
root:HTMLElement | string- Element (or CSS selector) used to disable the app's root to prevent background clicks.closeKey:string | string[]- Key or list of keys that close the top modal. Defaults to"Escape". Any validKeyboardEvent.keyvalue is accepted.
// Single key (default)
<ModalRenderer closeKey="Escape" />
// Custom key
<ModalRenderer closeKey="q" />
// Multiple keys
<ModalRenderer closeKey={["Escape", "q"]} />showModal options
id: string - If provided and already open, the modal is replaced.closeOnOutsideClick: boolean - Closes on backdrop click and the configuredcloseKey. Default:true.disableBackground: boolean - Disables the app's root layout to prevent clicks passing from the modal.ariaLabel: string - Applied to the dialog element for screen readers.ariaDescribedBy: string - ID of a descriptive element inside the modal content.onOpen: function - Callback fired when the modal opens.onClose: function - Callback fired when the modal begins closing.
DOM structure
The engine renders allows to edit the backdrop optional styling with CSS:
Example:
[modal-backdrop] {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}Accessibility
- Dialog uses
role="dialog"andaria-modal="true". ariaLabelandariaDescribedByoptions are forwarded to the dialog element.- Focus is trapped within the top modal while it is open.
- The top modal is auto-focused when it becomes active.
Behavior details
- Key dismissal - Pressing the configured
closeKey(default"Escape") closes the top modal. - Outside click - Clicking outside the modal when
closeOnOutsideClickistrueautomatically closes the active modal. - Scroll lock - Body scroll is disabled while any modal is open; scrollbar width is compensated via
padding-rightto prevent layout shift. - Disable Background - When a modal with
disableBackgroundis open, the app root blocks pointer and keyboard interaction behind the modal.
