A React hook that turns browser history into a native-like back stack, so modals, drawers and other overlays close with the back button instead of leaving the page.
| Before | After |
|
|
| Modals remain open when going back | Modals can be closed with back button |
- 🔙 Close modals, drawers, and other UI elements with the browser's back button
- 📚 When multiple overlays are open, they are closed in reverse order (last opened first)
- 🧹 Closing an overlay by any means (button, backdrop,
Escape) pops the history entry it pushed - 🔗 Keeps the current URL, including the hash, and preserves the
history.stateyour router owns - 📱 Provides a mobile app experience on the web similar to PWAs
- ⚛️ Works with any React framework, and is safe to render on the server
npm install use-back-stackimport { useState } from "react";
import { useBackStack } from "use-back-stack";
function App() {
// Drawer state management
const [isDrawer1Open, setIsDrawer1Open] = useState(false);
const [isDrawer2Open, setIsDrawer2Open] = useState(false);
// Using the hook
useBackStack([
{ isOpen: isDrawer1Open, setIsOpen: setIsDrawer1Open },
{ isOpen: isDrawer2Open, setIsOpen: setIsDrawer2Open },
]);
return (
<div>
<button onClick={() => setIsDrawer1Open(true)}>Open Drawer 1</button>
<button onClick={() => setIsDrawer2Open(true)}>Open Drawer 2</button>
{isDrawer1Open && (
<div className="drawer">
<h2>Drawer 1</h2>
<button onClick={() => setIsDrawer1Open(false)}>Close</button>
</div>
)}
{isDrawer2Open && (
<div className="drawer">
<h2>Drawer 2</h2>
<button onClick={() => setIsDrawer2Open(false)}>Close</button>
</div>
)}
</div>
);
}const close = useBackStack(overlays: { isOpen: boolean; setIsOpen: (isOpen: boolean) => void }[]);The hook keeps exactly one history entry per open overlay. Opening an overlay pushes an entry, and closing one
removes it — no matter whether it was closed by the back button or by your own setIsOpen(false).
The returned close() closes the topmost open overlay, which is handy for a shared close button or backdrop:
const close = useBackStack([{ isOpen, setIsOpen }]);
<div className="backdrop" onClick={close} />;This package used to be published as use-back-stack-overlay. Only the names changed — the arguments and the
return value are the same:
-import { useBackStackOverlay } from "use-back-stack-overlay";
+import { useBackStack } from "use-back-stack";
-useBackStackOverlay([{ isOpen, setIsOpen }]);
+useBackStack([{ isOpen, setIsOpen }]);- If your app navigates (for example
router.push) while an overlay is open, the entry on top of the stack no longer belongs to the hook. Closing the overlay then leaves the history untouched instead of navigating your user away. - Overlays that are still open when the component unmounts keep their history entries; the next back press consumes them without a visible change.

