Getting Started

This guide takes you from an empty React app to a modal that returns the user's choice to the code that opened it. It takes about five minutes.

Requirements

React Snappy Modal has no runtime dependencies besides React. The package declares React 16.12, 17, and 18 as peer dependencies, and this documentation site runs it on React 19.

Modals are opened with SnappyModal.show() and closed by settling a Promise, so you should be comfortable with async and await. TypeScript declarations ship with the package; there is no separate @types package.

1. Install the package

Install it with your package manager. The stylesheet is bundled and imported automatically when you import react-snappy-modal, so there is no CSS file to add.

bash
1npm install react-snappy-modal
2# or
3yarn add react-snappy-modal
4# or
5pnpm add react-snappy-modal

2. Add SnappyModalProvider

SnappyModalProvider is where open modals are rendered. Mount it once near the root of your app. Modals are rendered after the provider's children, so they appear on top of your page.

After that, SnappyModal.show() can be called from anywhere: components, event handlers, or plain utility functions outside React.

main.tsxtsx
1import { createRoot } from 'react-dom/client';
2import { SnappyModalProvider } from 'react-snappy-modal';
3import App from './App';
4
5createRoot(document.getElementById('root')!).render(
6  <SnappyModalProvider>
7    <App />
8  </SnappyModalProvider>,
9);

3. Write a modal component

A modal is an ordinary React component. Inside it, call useCurrentModal() to get resolveModal. Calling resolveModal(value) closes this exact modal and hands value to the code that opened it.

The library positions your content and draws the backdrop, but it adds no background, padding, or border to the content. Style the card yourself, as below.

ChooseColor.tsxtsx
1import { useCurrentModal } from 'react-snappy-modal';
2
3export function ChooseColor() {
4  const { resolveModal } = useCurrentModal();
5
6  return (
7    <div style={{ background: 'white', padding: 24, borderRadius: 12 }}>
8      <h2>Pick a color</h2>
9      <button onClick={() => resolveModal('red')}>Red</button>
10      <button onClick={() => resolveModal('blue')}>Blue</button>
11      <button onClick={() => resolveModal(null)}>Cancel</button>
12    </div>
13  );
14}

4. Open it and await the result

SnappyModal.show(element, options?) renders the element and returns a Promise. Your function pauses at await until the modal closes and then continues with the result. There is no isOpen state and no callback prop to wire up.

App.tsxtsx
1import { useState } from 'react';
2import SnappyModal from 'react-snappy-modal';
3import { ChooseColor } from './ChooseColor';
4
5export default function App() {
6  const [color, setColor] = useState('none');
7
8  const pickColor = async () => {
9    const result = await SnappyModal.show(<ChooseColor />);
10    // Runs only after the modal is closed
11    if (result) setColor(result);
12  };
13
14  return <button onClick={pickColor}>Selected: {color}</button>;
15}

What the Promise returns

The result depends on how the modal was closed:

How the modal closedResult
resolveModal(value) or SnappyModal.close(value)Resolves with value
Click on the backdrop (when allowOutsideClick is true, the default)Resolves with undefined
rejectModal(error) or SnappyModal.throw(error)Rejects with error; handle it with try/catch

How it works

Under the hood, a single call goes through four steps:

  • show() adds the modal to an internal stack, gives it a unique modalId, and returns a pending Promise.
  • SnappyModalProvider re-renders and draws every modal in the stack, ordered by layer.
  • Your component calls resolveModal(). The modal is removed from the stack and its Promise settles.
  • The code after await runs with the result.

Troubleshooting

Most setup problems fall into one of these cases:

SymptomCause and fix
Nothing appears and the code after await never runsSnappyModalProvider is not mounted. Make sure it wraps the tree that is on screen. The Promise stays pending until the modal is closed.
Error: useCurrentModal must be used within CurrentModalProvideruseCurrentModal() only works in components rendered by SnappyModal.show(). Move the hook into the modal component, or close from outside with SnappyModal.close(value).
The modal content is transparentThe content wrapper only handles positioning. Give your root element a background and padding, or pass className or style in the options.
The modal shows up under a sticky headerModals use z-index: 1000 by default. Pass a larger zIndex option or lower the other element's z-index.
The page behind the modal cannot scrollallowScroll is false by default: the page is locked while the modal is open and the scroll position is restored on close. Pass allowScroll: true for non-blocking panels.
NextAPI Reference →