npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rokku-x/react-hook-modal

v0.10.1

Published

A lightweight, powerful, and flexible React modal hook library that supports stacking and multi-window modals, with multiple rendering modes, dynamic and static modal support.

Downloads

351

Readme

react-hook-modal

CI npm version license downloads TS

A lightweight, powerful, and flexible React modal hook library that supports stacking and multi-window modals, with multiple rendering modes, dynamic and static modal support, and zero non-core dependencies.

Installation

npm install @rokku-x/react-hook-modal
# or
bun add @rokku-x/react-hook-modal
# or
yarn add @rokku-x/react-hook-modal
# or
pnpm add @rokku-x/react-hook-modal

Features

  • 🎯 Multiple Render Modes - STACKED, CURRENT_ONLY, CURRENT_HIDDEN_STACK
  • 🧱 Stacking & Multi-Window - Open multiple modals at once and control focus/order
  • Static and Dynamic Modals - Choose the best approach for your use case
  • 🪝 React Hooks API - Easy-to-use hook-based interface
  • 📦 TypeScript Support - Full type safety out of the box
  • 🎨 Customizable Styling - Extensive styling props for complete control
  • 🔹 Versatile Overlays - Use it to build dialogs, alerts, popups, prompts, forms, loading spinners, UI blockers, and more.
  • Auto-injected Styles - CSS automatically injected, no separate import needed
  • 🪶 Lightweight - Under 3 kB gzipped, tree-shakeable with modular build
  • Accessibility - Built-in support for scroll prevention and inert attribute

Use Cases

  • Confirmation & Alerts — Small confirm/cancel dialogs for destructive actions and important messages.
  • Prompts & Forms — Collect user input using forms rendered inside modals (supports dynamic portals or static form content).
  • Loading & Activity Spinners — Full-screen or localized spinners during async operations; show one-off instances while loading.
  • UI Blockers & Backdrops — Block user interaction and display status or retry actions when needed.
  • Popups & Tool Panels — Lightweight contextual overlays or tool panels rendered into a modal window.
  • Stacked & Multi-instance Modals — Open multiple stacked modals or use multiple BaseModalRenderer instances (via rendererId) to scope overlays to different app areas, apply different styles to different renderers.

Quick Start

1. Setup the Base Modal Renderer

First, add the BaseModalRenderer at the root of your application:

import { BaseModalRenderer, RenderMode } from '@rokku-x/react-hook-modal';

function App() {
  return (
    <>
      <YourComponents />
      <BaseModalRenderer renderMode={RenderMode.STACKED} />
    </>
  );
}

You can mount multiple BaseModalRenderer instances — each must have a unique id to use an isolated modal store. When using hooks, pass rendererId to target a specific renderer's store. Example:

// Mount two separate renderers (different areas of the app)
<BaseModalRenderer id="sidebar-modals" />
<BaseModalRenderer id="dialog-modals" />

// Target a renderer from a hook
const [showSidebarModal] = useStaticModal({ rendererId: 'sidebar-modals' })
const [renderDialog, openDialog] = useDynamicModal({ rendererId: 'dialog-modals' })

2. Use the Modal Hook

import { useStaticModal } from '@rokku-x/react-hook-modal';

function MyComponent() {
  const [showModal, closeModal] = useStaticModal();
  
  return (
    <button onClick={() => showModal(
      <div className="modal-content">
        <h2>Hello Modal!</h2>
        <button onClick={closeModal}>Close</button>
      </div>
    )}>
      Open Modal
    </button>
  );
}

API Reference

Hooks Summary

| Hook | Args | Returns | Description | |------|------|---------|-------------| | useBaseModal | { rendererId?: string } | BaseModal actions & state | Low-level store access for advanced operations. Pass rendererId to target a renderer instance. | | useStaticModal | { rendererId?: string; element?: AcceptableElement } | [showModal, closeModal, focus, updateModalContent, id] | Static modal hook; open named or one-off instances, default content via element. Use rendererId to target a specific renderer. | | useDynamicModal | { rendererId?: string } | [renderModalElement, showModal, closeModal, focus, id, isForeground] | Dynamic modal hook (portal-rendered content). showModal returns the modal id string. |

BaseModalRenderer

The main component that renders all modals in your application. Must be mounted at the root level.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | id | string | 'base-modal-wrapper' | Unique identifier for the modal wrapper for creating isolated modal stores instance. | | renderMode | RenderMode | RenderMode.STACKED | Determines how multiple modals are rendered | | style | CSSProperties | undefined | Inline styles for the dialog element | | className | string | undefined | CSS class for the dialog element | | windowClassName | string | undefined | CSS class applied to each modal window | | windowStyle | CSSProperties | undefined | Inline styles for each modal window | | disableBackgroundScroll | boolean | true | Prevents body scroll when modal is open |

Render Modes

| Mode | Behavior | |------|----------| | STACKED | All modals are visible and stacked on top of each other | | CURRENT_ONLY | Only the topmost modal is visible | | CURRENT_HIDDEN_STACK | All modals are in the DOM but only the topmost is visible (best for preserving state) |

Helper Components: ModalBackdrop & ModalWindow

Two small presentational components are exported for convenience to simplify markup and styling.

  • ModalBackdrop — A full-screen backdrop used to wrap the background and capture clicks (commonly used to close the modal). Accepts standard div props: onClick, className, style, etc.
  • ModalWindow — A styled window container used to render modal content. Accepts standard div props and prevents event propagation from clicks inside the window.

Example usage:

import { useStaticModal, ModalBackdrop, ModalWindow, BaseModalRenderer } from '@rokku-x/react-hook-modal';

function BackdropExample() {
  const [showModal, closeModal] = useStaticModal();

  return (
    <button onClick={() => showModal(
      <ModalBackdrop onClick={closeModal}>
        <ModalWindow>
          <h2>Title</h2>
          <p>Content inside modal window.</p>
          <button onClick={closeModal}>Close</button>
        </ModalWindow>
      </ModalBackdrop>
    )}>
      Show Modal with Backdrop
    </button>
  )
}

Using these helper components keeps modal markup consistent across your app and makes it easier to style/backdrop behavior.

useStaticModal

Hook for displaying static modal content (content set when the modal is opened).

Returns

[showModal, closeModal, focus, updateModalContent, id]

| Return Value | Type | Description | |---|---|---| | showModal(el?: AcceptableElement, id?: string) | (el?: AcceptableElement, id?: string) => string | Show a modal and target/reopen an instance with the provided id. Passing the same id will update/replace that instance. Returns the hook's id. | | showModal(el?: AcceptableElement, createNew?: boolean) | (el?: AcceptableElement, createNew?: boolean) => string | Show a modal and create a new unique instance when createNew === true. Omit or pass false to use the hook's own id (single-instance default). Returns the hook's id. | | closeModal | (customId?: string) => boolean | Close the given modal instance (defaults to the hook's id) | | focus | (customId?: string) => boolean | Bring the given modal to the foreground (defaults to the hook's id) | | updateModalContent | (newContent: AcceptableElement, customId?: string) => boolean | Update the content of the specified modal instance without closing | | id | string | Unique identifier assigned to this hook instance |

Parameters

| Parameter | Type | Default | Description | |---|---|---|---| | element | AcceptableElement | undefined | Default content to display if showModal is called without arguments |

💡 Tip: You can now open multiple instances from a single useStaticModal hook by passing a custom id or true to showModal. Passing the same custom id will update/replace that instance instead of creating a new one.

Overloads & instance control

  • showModal(el?: AcceptableElement, id?: string) — Open or target a named instance. Passing the same id will update/replace that instance.
  • showModal(el?: AcceptableElement, createNew?: boolean) — Pass true to create a new unique instance each call. Omit or pass false to use the hook's own (single) id.

Examples:

const [showModal, closeModal, focus, updateModalContent, id] = useStaticModal()

// Use hook id (single-instance)
showModal(<div>Default</div>)

// Create a unique one-off instance
const oneOffHookId = showModal(<div>One-off</div>, true)
console.log('one-off created by hook id:', oneOffHookId)

// Open or update a named instance
const myModalHookId = showModal(<div>Named</div>, 'my-modal')
console.log('named instance hook id (always hook id):', myModalHookId)

// Update a named instance's content later
updateModalContent(<div>Updated</div>, 'my-modal')

// Close a named instance
closeModal('my-modal')

Note: showModal returns the hook's id (string) — the generated or hook id, not a close function.

Migration (from older versions)

If you used the old return signature or relied on showModal returning a close function, update your code as follows:

- const [show, close, focus] = useStaticModal()
- const closeFn = show(<div>Hi</div>)
+ const [show, close, focus, updateModalContent, id] = useStaticModal()
+ const hookId = show(<div>Hi</div>) // returns hook id string
+ // for one-off instances use: show(<div>One-off</div>, true)
+ // for named instances use: show(<div>Named</div>, 'custom-id')

The new overloads also allow opening multiple live static instances via custom ids or true for one-off instances.

useDynamicModal

Parameters

| Name | Type | Default | Description | |------|------|---------|-------------| | rendererId | string | 'default' | ID of the BaseModalRenderer to target. Use to scope dynamic modals to a specific renderer instance. |

Returns

| Return | Type | Description | |--------|------|-------------| | renderModalElement | (el: ReactNode) => ReactNode | Renders content into the modal window via a portal (returns a React portal when renderer is mounted). | | showModal | () => string | Opens the modal and returns the modal id string. | | closeModal | () => boolean | Closes the modal instance. | | focus | () => boolean | Brings this modal to the foreground. | | id | string | Hook instance id. | | isForeground | boolean | Whether this modal is currently focused. |

Hook for rendering dynamic modal content (content rendered from within the modal). Use the rendererId option to scope dynamic modals to a specific renderer instance.

useBaseModal

Parameters

| Name | Type | Default | Description | |------|------|---------|-------------| | rendererId | string | 'default' | The rendererId identifies which BaseModalRenderer store to target. |

Returns

| Action/Property | Type | Description | |-----------------|------|-------------| | pushModal | (modalId: string, el: AcceptableElement, isDynamic?: boolean) => string | Pushes a modal into the store and returns the modal id. | | popModal | (modalId: string) => boolean | Removes a modal by id. | | updateModal | (modalId: string, newContent: AcceptableElement) => boolean | Updates content for static modals. | | focusModal | (modalId: string) => boolean | Moves a modal to the top of the stack. | | getModal | (modalId: string) => [AcceptableElement | null, boolean] | undefined | Retrieves a modal entry. | | getModalWindowRef | (modalId: string) => HTMLDivElement | undefined | Returns the modal window DOM node for a given id. | | getModalOrderIndex | (modalId: string) => number | Returns the modal order index (zero-based). | | currentModalId | string? | Id of the modal currently in focus. | | modalStackMap | Map<string, [AcceptableElement | null, boolean]> | Internal stack map of modal entries. |

Low-level hook for direct modal store access for creating advanced custom modals. Used internally by other hooks.

Examples

Quick Examples

Confirm / Alert

function ConfirmExample() {
  const [showModal, closeModal] = useStaticModal()
  const openConfirm = () => showModal(
    <div>
      <h2>Confirm Delete</h2>
      <p>Are you sure you want to delete this?</p>
      <button onClick={() => { console.log('deleted'); closeModal(); }}>Delete</button>
      <button onClick={closeModal}>Cancel</button>
    </div>
  )
  return <button onClick={openConfirm}>Delete</button>
}

Loading Spinner

function LoadingExample() {
  const [showModal, closeModal] = useStaticModal()
  const runTask = async () => {
    const spinnerId = showModal(<div className="spinner">Loading…</div>, true)
    await someAsyncWork()
    closeModal(spinnerId)
  }
  return <button onClick={runTask}>Run Task</button>
}

Prompt / Form (dynamic)

function PromptExample() {
  const [renderModalElement, showModal, closeModal] = useDynamicModal()
  const onOpen = () => showModal()
  return (
    <>
      <button onClick={onOpen}>Open Prompt</button>
      {renderModalElement(
        <form onSubmit={(e) => { e.preventDefault(); /* read value */ closeModal() }}>
          <input name="value" placeholder="Enter value" />
          <button type="submit">OK</button>
          <button type="button" onClick={closeModal}>Cancel</button>
        </form>
      )}
    </>
  )
}

Example 1: Basic Static Modal

import { useStaticModal, BaseModalRenderer, ModalBackdrop, ModalWindow } from '@rokku-x/react-hook-modal';

function App() {
  return (
    <>
      <BasicModalExample />
      <BaseModalRenderer />
    </>
  );
}

function BasicModalExample() {
  const [showModal, closeModal] = useStaticModal();
  
  return (
    <button onClick={() => showModal(
      <ModalBackdrop onClick={closeModal}>
        <ModalWindow style={{ padding: '20px', background: 'white', borderRadius: '8px' }}>
          <h2>Welcome</h2>
          <p>This is a basic modal example.</p>
          <button onClick={closeModal}>Close</button>
        </ModalWindow>
      </ModalBackdrop>
    )}>
      Show Modal
    </button>
  );
}

Example 1b: useStaticModal - Content in Hook Parameter

Pass the modal content directly to the useStaticModal hook. Call showModal() without arguments to display it.

import { useStaticModal, BaseModalRenderer, ModalBackdrop, ModalWindow } from '@rokku-x/react-hook-modal';

function WelcomeModal() {
  const [showModal, closeModal] = useStaticModal({
    element: (
      <ModalBackdrop>
        <ModalWindow style={{ padding: '20px', background: 'white', borderRadius: '8px' }}>
          <h2>Welcome</h2>
          <p>This modal content is defined in the hook parameter.</p>
          <button onClick={closeModal}>Close</button>
        </ModalWindow>
      </ModalBackdrop>
    )
  });
  
  return <button onClick={showModal}>Show Welcome Modal</button>;
}

function App() {
  return (
    <>
      <WelcomeModal />
      <BaseModalRenderer />
    </>
  );
}

Example 1c: useStaticModal - Content in showModal Call

Pass the modal content when calling showModal(). This approach is more flexible for dynamic content.

import { useStaticModal, BaseModalRenderer, ModalBackdrop, ModalWindow } from '@rokku-x/react-hook-modal';

function DynamicContentModal() {
  const [showModal, closeModal] = useStaticModal();
  
  const showWelcome = () => {
    showModal(
      <ModalBackdrop onClick={closeModal}>
        <ModalWindow style={{ padding: '20px', background: 'white', borderRadius: '8px' }}>
          <h2>Welcome</h2>
          <p>This modal content is provided when calling showModal().</p>
          <button onClick={closeModal}>Close</button>
        </ModalWindow>
      </ModalBackdrop>
    );
  };
  
  const showGoodbye = () => {
    showModal(
      <ModalBackdrop onClick={closeModal}>
        <ModalWindow style={{ padding: '20px', background: 'white', borderRadius: '8px' }}>
          <h2>Goodbye</h2>
          <p>Different content can be shown by calling showModal with different content.</p>
          <button onClick={closeModal}>Close</button>
        </ModalWindow>
      </ModalBackdrop>
    );
  };
  
  return (
    <>
      <button onClick={showWelcome}>Show Welcome</button>
      <button onClick={showGoodbye}>Show Goodbye</button>
    </>
  );
}

function App() {
  return (
    <>
      <DynamicContentModal />
      <BaseModalRenderer />
    </>
  );
}

Example 2: Confirmation Dialog

import { useStaticModal, BaseModalRenderer, ModalBackdrop, ModalWindow } from '@rokku-x/react-hook-modal';

function ConfirmationExample() {
  const [showModal, closeModal] = useStaticModal();
  
  const handleDelete = () => {
    showModal(
      <ModalBackdrop onClick={closeModal}>
        <ModalWindow style={{ padding: '20px', background: 'white', borderRadius: '8px', maxWidth: '400px' }}>
          <h2>Confirm Delete</h2>
          <p>Are you sure you want to delete this item?</p>
          <div style={{ display: 'flex', gap: '10px', justifyContent: 'flex-end' }}>
            <button onClick={closeModal}>Cancel</button>
            <button 
              onClick={() => {
                console.log('Deleted!');
                closeModal();
              }}
              style={{ background: '#dc3545', color: 'white' }}
            >
              Delete
            </button>
          </div>
        </ModalWindow>
      </ModalBackdrop>
    );
  };
  
  return <button onClick={handleDelete}>Delete Item</button>;
}

function App() {
  return (
    <>
      <ConfirmationExample />
      <BaseModalRenderer />
    </>
  );
}

Example 3: Update Modal Content

import { useStaticModal, BaseModalRenderer, ModalBackdrop, ModalWindow } from '@rokku-x/react-hook-modal';
import { useState } from 'react';

function UpdateableModalExample() {
  const [showModal, closeModal, focus, updateModalContent, modalId] = useStaticModal();
  const [step, setStep] = useState(0);
  
  const openWizard = () => {
    setStep(0);
    showModal(
      <ModalBackdrop>
        <ModalWindow style={{ padding: '20px', background: 'white', borderRadius: '8px', maxWidth: '500px' }}>
          {renderStep(0)}
        </ModalWindow>
      </ModalBackdrop>
    );
  };
  
  const renderStep = (stepNum: number) => (
    <div>
      <h2>Step {stepNum + 1} of 3</h2>
      <p>This is step {stepNum + 1} content.</p>
      <div style={{ display: 'flex', gap: '10px', marginTop: '20px' }}>
        <button 
          onClick={() => {
            const newStep = stepNum - 1;
            setStep(newStep);
            updateModalContent(renderStep(newStep));
          }}
          disabled={stepNum === 0}
        >
          Previous
        </button>
        <button 
          onClick={() => {
            if (stepNum === 2) {
              console.log('Wizard completed!');
              closeModal();
            } else {
              const newStep = stepNum + 1;
              setStep(newStep);
              updateModalContent(renderStep(newStep));
            }
          }}
        >
          {stepNum === 2 ? 'Complete' : 'Next'}
        </button>
      </div>
    </div>
  );
  
  return <button onClick={openWizard}>Open Wizard</button>;
}

function App() {
  return (
    <>
      <UpdateableModalExample />
      <BaseModalRenderer />
    </>
  );
}

Example 4: Dynamic Modal with Form

import { useDynamicModal, BaseModalRenderer, ModalBackdrop, ModalWindow } from '@rokku-x/react-hook-modal';
import { useState } from 'react';

function FormModal() {
  const [renderModalElement, showModal, closeModal, focus, id, isForeground] = useDynamicModal();
  const [formData, setFormData] = useState({ name: '', email: '' });
  
  const openModal = () => {
    showModal();
  };
  
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('Form submitted:', formData);
    closeModal();
  };
  
  const modalContent = (
    <ModalBackdrop onClick={closeModal}>
      <ModalWindow style={{ padding: '20px', background: 'white', borderRadius: '8px', maxWidth: '400px' }}>
        <form onSubmit={handleSubmit}>
          <h2>Contact Form</h2>
          <input
            type="text"
            placeholder="Name"
            value={formData.name}
            onChange={(e) => setFormData({ ...formData, name: e.target.value })}
            style={{ width: '100%', marginBottom: '10px', padding: '8px' }}
          />
          <input
            type="email"
            placeholder="Email"
            value={formData.email}
            onChange={(e) => setFormData({ ...formData, email: e.target.value })}
            style={{ width: '100%', marginBottom: '10px', padding: '8px' }}
          />
          <div style={{ display: 'flex', gap: '10px' }}>
            <button type="submit">Submit</button>
            <button type="button" onClick={closeModal}>Cancel</button>
          </div>
        </form>
      </ModalWindow>
    </ModalBackdrop>
  );
  
  return (
    <div>
      <button onClick={openModal}>Open Form</button>
      {renderModalElement(modalContent)}
    </div>
  );
}

function App() {
  return (
    <>
      <FormModal />
      <BaseModalRenderer />
    </>
  );
}

Example 5: Multiple Stacked Modals

import { useStaticModal, BaseModalRenderer, RenderMode, ModalBackdrop, ModalWindow } from '@rokku-x/react-hook-modal';

function StackedModalsExample() {
  const [showModal1, closeModal1] = useStaticModal();
  const [showModal2, closeModal2] = useStaticModal();
  
  const openFirstModal = () => {
    showModal1(
      <ModalBackdrop onClick={closeModal1}>
        <ModalWindow style={{ padding: '20px', background: 'white', borderRadius: '8px', maxWidth: '400px' }}>
          <h2>First Modal</h2>
          <p>This is the first modal.</p>
          <button onClick={() => openSecondModal()}>Open Another</button>
          <button onClick={closeModal1}>Close</button>
        </ModalWindow>
      </ModalBackdrop>
    );
  };
  
  const openSecondModal = () => {
    showModal2(
      <ModalBackdrop onClick={closeModal2}>
        <ModalWindow style={{ padding: '20px', background: '#f0f0f0', borderRadius: '8px', maxWidth: '400px' }}>
          <h2>Second Modal</h2>
          <p>This modal is stacked on top of the first one!</p>
          <button onClick={closeModal2}>Close This Modal</button>
        </ModalWindow>
      </ModalBackdrop>
    );
  };
  
  return <button onClick={openFirstModal}>Open Stacked Modals</button>;
}

function App() {
  return (
    <>
      <StackedModalsExample />
      <BaseModalRenderer renderMode={RenderMode.STACKED} />
    </>
  );
}

Example 5b: Multiple Named & One-off Instances (useStaticModal)

import { useStaticModal, BaseModalRenderer, RenderMode, ModalBackdrop, ModalWindow } from '@rokku-x/react-hook-modal';

function MultipleInstancesExample() {
  const [showModal, closeModal] = useStaticModal();

  const openTwoDifferent = () => {
    // Use custom ids to open/target distinct modal instances
    showModal(
      <ModalBackdrop onClick={() => closeModal('first-modal')}>
        <ModalWindow style={{ padding: '20px', background: 'white', borderRadius: '8px', maxWidth: '400px' }}>
          <h2>First modal</h2>
          <p>A modal opened using a custom id.</p>
          <button onClick={() => closeModal('first-modal')}>Close</button>
        </ModalWindow>
      </ModalBackdrop>,
      'first-modal'
    );

    showModal(
      <ModalBackdrop onClick={() => closeModal('second-modal')}>
        <ModalWindow style={{ padding: '20px', background: '#f6f6f6', borderRadius: '8px', maxWidth: '400px' }}>
          <h2>Second modal</h2>
          <p>Another modal opened with a different id.</p>
          <button onClick={() => closeModal('second-modal')}>Close</button>
        </ModalWindow>
      </ModalBackdrop>,
      'second-modal'
    );
  };

  const openOneOff = () => {
    // Pass `true` to create a new unique instance each time
    showModal(
      <ModalBackdrop>
        <ModalWindow style={{ padding: '20px', background: 'white', borderRadius: '8px', maxWidth: '400px' }}>
          <h2>One-off</h2>
          <p>This creates a unique instance every time (pass `true`).</p>
          <button onClick={closeModal}>Close</button>
        </ModalWindow>
      </ModalBackdrop>,
      true
    );
  };

  return (
    <>
      <button onClick={openTwoDifferent}>Open Two Different Modals</button>
      <button onClick={openOneOff}>Open One-off Modal</button>
    </>
  );
}

function App() {
  return (
    <>
      <MultipleInstancesExample />
      <BaseModalRenderer renderMode={RenderMode.STACKED} />
    </>
  );
}

Example 5c: Named & One-off Instances (useStaticModal)

import { useStaticModal, BaseModalRenderer, ModalBackdrop, ModalWindow } from '@rokku-x/react-hook-modal';

function MultipleInstancesAdvanced() {
  const [showModal, closeModal, focus, updateModalContent, id] = useStaticModal();

  const openNamed = () => {
    // Open/replace a named instance called 'named-a'
    showModal(
      <ModalBackdrop onClick={() => closeModal('named-a')}>
        <ModalWindow style={{ padding: '20px', background: 'white' }}>
          <h2>Named A</h2>
          <p>This modal is opened with a custom id ('named-a').</p>
          <button onClick={() => closeModal('named-a')}>Close Named A</button>
        </ModalWindow>
      </ModalBackdrop>,
      'named-a'
    );
  };

  const openOneOff = () => {
    // Create a one-off unique instance each time by passing `true`
    const oneOffHookId = showModal(
      <ModalBackdrop>
        <ModalWindow style={{ padding: '20px', background: '#f7f7f7' }}>
          <h2>One-off</h2>
          <p>This instance is unique every call (created by passing <code>true</code>).</p>
        </ModalWindow>
      </ModalBackdrop>,
      true
    );
    console.log('one-off created by hook id:', oneOffHookId);
  };

  const updateNamed = () => {
    updateModalContent(
      <ModalBackdrop>
        <ModalWindow style={{ padding: '20px', background: 'white' }}>
          <h2>Named A (Updated)</h2>
          <p>Content was updated programmatically via <code>updateModalContent</code>.</p>
        </ModalWindow>
      </ModalBackdrop>,
      'named-a'
    );
  };

  return (
    <div style={{ display: 'flex', gap: 8 }}>
      <button onClick={openNamed}>Open/Update Named A</button>
      <button onClick={openOneOff}>Open One-off</button>
      <button onClick={updateNamed}>Update Named A</button>
      <div style={{ alignSelf: 'center', marginLeft: 12, color: '#666' }}>hook id: <code>{id}</code></div>
    </div>
  );
}

function App() {
  return (
    <>
      <MultipleInstancesAdvanced />
      <BaseModalRenderer />
    </>
  );
}

This example shows how to open named instances (by passing a string id) and how to create one-off unique instances (by passing true). It also demonstrates using the returned id for reference in your UI.

Example 6: Custom Styling

import { useStaticModal, BaseModalRenderer, ModalBackdrop, ModalWindow } from '@rokku-x/react-hook-modal';

function StyledModalExample() {
  const [showModal, closeModal] = useStaticModal();
  
  return (
    <>
      <button onClick={() => showModal(
        <ModalBackdrop onClick={closeModal}>
          <ModalWindow style={{
            padding: '30px',
            background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
            borderRadius: '12px',
            color: 'white',
            textAlign: 'center',
            maxWidth: '500px',
            boxShadow: '0 10px 40px rgba(0,0,0,0.3)'
          }}>
            <h2>Styled Modal</h2>
            <p>This modal has custom styling!</p>
            <button 
              onClick={closeModal}
              style={{
                background: 'white',
                color: '#667eea',
                border: 'none',
                padding: '10px 20px',
                borderRadius: '6px',
                cursor: 'pointer',
                fontWeight: 'bold'
              }}
            >
              Close
            </button>
          </ModalWindow>
        </ModalBackdrop>
      )}>
        Show Styled Modal
      </button>
      <BaseModalRenderer 
        windowClassName="custom-modal-window"
        windowStyle={{ backdropFilter: 'blur(5px)' }}
        className="custom-modal-wrapper"
      />
    </>
  );
}

Types

RenderMode

enum RenderMode {
  STACKED = 0,              // All modals visible and stacked
  CURRENT_ONLY = 1,         // Only topmost modal visible
  CURRENT_HIDDEN_STACK = 2  // All in DOM but only topmost visible
}

AcceptableElement

type AcceptableElement = React.ReactNode | (() => React.ReactNode);

Best Practices

  1. Always mount BaseModalRenderer at the root - Place it at the top level of your application
  2. Choose the right render mode - Use STACKED for independent modals, CURRENT_HIDDEN_STACK to preserve state
  3. Use static modals for simple content - Faster and simpler than dynamic modals
  4. Use dynamic modals for complex interactions - Better for forms and interactive content
  5. Keep modals accessible - Provide keyboard navigation and ARIA attributes
  6. Customize styling carefully - Use windowClassName and windowStyle for consistent styling

Bundle Size

  • ESM: ~2.83 kB gzipped (7.71 kB raw)
  • CJS: ~2.78 kB gzipped (8.56 kB raw)

Measured with Vite build for v0.9.2 (includes auto-injected CSS, total size of all modules).

Runtime Performance

Measured locally (jsdom, React 18, macOS) using a microbenchmark:

  • Push/Pop: ~7.3 µs per op
  • Update Content: ~2.4 µs per op
  • Focus Modal: ~1.1 µs per op
  • Dynamic Portal Render: ~0.5 µs per call

Notes:

  • Numbers are approximate and depend on device/load.
  • Portal render figures reflect the cost of creating the portal when the modal window ref exists.

Library Load Time

Measured in Node (dynamic import/require):

  • CJS require: ~20.5 ms
  • ESM import: ~38.2 ms

See perf harnesses under src/__tests__/perf.test.ts and src/__tests__/perf-load.test.ts.

License

MIT