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.
1npm install react-snappy-modal
2# or
3yarn add react-snappy-modal
4# or
5pnpm add react-snappy-modal2. 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.
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.
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.
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 closed | Result |
|---|---|
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 uniquemodalId, and returns a pending Promise.SnappyModalProviderre-renders and draws every modal in the stack, ordered bylayer.- Your component calls
resolveModal(). The modal is removed from the stack and its Promise settles. - The code after
awaitruns with the result.
Troubleshooting
Most setup problems fall into one of these cases:
| Symptom | Cause and fix |
|---|---|
Nothing appears and the code after await never runs | SnappyModalProvider 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 CurrentModalProvider | useCurrentModal() 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 transparent | The 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 header | Modals 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 scroll | allowScroll 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. |