A react-toast-notifications alternative that runs on React 19
react-toast-notifications stopped at React 17, and on React 19 the first addToast call throws. Toast Stack is a free, MIT licensed toast component you add with one shadcn command, and the small shim below keeps your old addToast calls compiling while you move over.
import { Toaster } from "@/components/toast-stack/ToastStack";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Toaster />
</body>
</html>
);
}
lib/use-toasts.ts (the migration shim)
"use client";
import type { ReactNode } from "react";
import { toast } from "@/components/toast-stack/ToastStack";
// Same names as react-toast-notifications, so old call sites keep compiling while you migrate.
type Appearance = "success" | "error" | "warning" | "info";
type AddToastOptions = { appearance?: Appearance; autoDismiss?: boolean; autoDismissTimeout?: number };
// Toast Stack always times out. Ten minutes stands in for the old "stay until closed".
const STAY_MS = 10 * 60 * 1000;
export function useToasts() {
return {
addToast(content: ReactNode, options: AddToastOptions = {}, callback?: (id: number) => void) {
const kind = options.appearance === "warning" ? "error" : options.appearance ?? "info";
const duration = options.autoDismiss === false ? STAY_MS : options.autoDismissTimeout;
const id = toast[kind](content, { duration });
callback?.(id);
return id;
},
removeToast: (id: number) => toast.dismiss(id),
removeAllToasts: () => toast.clear(),
};
}
app/settings/save-button.tsx (a promise toast and an action button)
"use client";
import { toast } from "@/components/toast-stack/ToastStack";
import { saveProfile, restoreProject } from "./actions";
export function SaveButton() {
return (
<button
onClick={() =>
toast.promise(saveProfile(), {
loading: { title: "Saving your profile" },
success: (profile) => ({ title: "Saved", description: `${profile.name} is up to date` }),
error: () => ({ title: "Couldn't save", description: "Check your connection and try again" }),
})
}
>
Save
</button>
);
}
export function ArchivedToast({ projectId }: { projectId: string }) {
return (
<button
onClick={() =>
toast.success("Project archived", {
action: { label: "Undo", onClick: () => void restoreProject(projectId) },
})
}
>
Archive
</button>
);
}
Good to know
react-toast-notifications' own README now opens with "Not Maintained" and points people elsewhere. The last release is 2.5.1 from July 8, 2021, and its peer range is React ^16.8 or ^17. npm counted 22,344 downloads in the week of October 16 to 22, 2026, so a lot of apps still ship it.
What we measured on October 26, 2026, rendering each package with React 19.2.9 and react-dom/client in jsdom: react-toast-notifications throws "findDOMNode is not a function" the moment addToast runs. react-s-alert throws "render is not a function" when Alert.success runs, because ReactDOM.render is gone in React 19. react-notification-system doesn't mount at all; it still uses string refs, and React 19 rejects them with "Expected ref to be a function, an object returned by React.createRef(), or undefined/null".
Toast Stack keeps one store for the whole app, so toast() works from any client component without a provider or a hook. Mount one <Toaster />. A second one would draw the same toasts twice.
What Toast Stack doesn't do: toasts sit bottom right on desktop and full width at the bottom on phones, and there is no placement prop. There's no newestOnTop (the newest toast is always in front), no components prop for a custom Toast (you own the file, so edit it), no portalTargetSelector (use strategy="absolute" inside a positioned parent instead), and no updateToast apart from the loading-to-done change in toast.promise. There's no separate warning style; the shim sends warnings to the error style, which uses a triangle icon. IDs are numbers, not strings.
The three old toast libraries and their React 19 error
react-toast-notifications 2.5.1, last release July 8, 2021, README says "Not Maintained": "findDOMNode is not a function" on the first addToast.
react-notification-system 0.4.0, last release May 6, 2020, peer range up to React 16: fails to mount ("Expected ref to be a function, an object returned by React.createRef(), or undefined/null").
react-s-alert 1.4.1, last release January 22, 2018, marked deprecated on npm ("No longer maintained, see more info in readme"): "render is not a function" on Alert.success.
Their peer ranges stop at React 16 or 17, so npm refuses them next to React 19 unless you pass --legacy-peer-deps. Forcing the install gets you the errors above at runtime.
react-notifications-component is a different case. Its last release is 4.0.1 from May 22, 2022, its peer range is React ^18.0.1, and npm counted 17,962 downloads in the week of October 16 to 22, 2026. Next to React 19, npm stops with ERESOLVE. Forced in with --legacy-peer-deps, it mounted and showed a notification on React 19 in our jsdom test (not a real browser), so the install is the problem we saw, not the code. If you'd rather not carry --legacy-peer-deps, Toast Stack above needs only framer-motion, whose peer range includes React 19.
react-toast-notifications to Toast Stack, call by call
<ToastProvider> around the app: delete it and put one <Toaster /> in app/layout.tsx.
const { addToast } = useToasts() then addToast("Saved", { appearance: "success" }): toast.success("Saved"), which you can call outside components too.
appearance: "error" or "info": toast.error() or toast.info(). "warning" has no style of its own; use toast.error().
autoDismissTimeout: { duration } per toast, or duration on the Toaster for all of them.
removeToast(id) and removeAllToasts(): toast.dismiss(id) and toast.clear().
placement, newestOnTop, components: no match (see Good to know above).
New: toast.promise() for loading, success and error in one toast, and action: { label, onClick } for an Undo or Retry button.
The most common job for a toast in a CRUD app is "Deleted. Undo?". Toast Stack can do that with an action button, as in save-button.tsx above.
Hold to Delete is the paid version of that moment as one piece: the user presses and holds to confirm, the file card flies out, and an undo toast with its own timer appears. onUndo puts it back; onCommit fires when the timer runs out, which is where you delete for real.
It draws a single file card with its own toast, so it doesn't replace a toast system. Use it next to Toast Stack on the screens where people delete things.
No. Its README opens with "Not Maintained", and the last release was 2.5.1 in July 2021. Its peer range stops at React 17.
Does react-toast-notifications work with React 19?
Not in our test. It installs with --legacy-peer-deps, then throws "findDOMNode is not a function" the first time addToast runs.
Does react-notifications-component work with React 19?
npm won't install it next to React 19 without --legacy-peer-deps, because its peer range stops at React 18. With that flag it mounted and showed a notification on React 19 in our jsdom test, which isn't a real browser.
Do I need a provider like ToastProvider?
No. Mount <Toaster /> once in your root layout and call toast() from any client component.
Can I keep my addToast calls while I migrate?
Yes. The use-toasts.ts shim above keeps addToast, removeToast and removeAllToasts working on top of Toast Stack. Swap call sites over to toast() whenever you like, then delete the shim.
Can a toast show a promise?
Yes. toast.promise shows a spinner, then turns into a check or an error when the promise settles.
Can I put toasts at the top of the screen?
Not with a prop. Positions live in toast-stack.css, so you'd change them there.
Is it really free?
Yes, MIT licensed, with source on GitHub. Hold to Delete, mentioned above, is a paid piece.
Does it work in Next.js App Router?
Yes. The file is marked "use client", so the layout can stay a server component and still render <Toaster />.
Can Cursor or v0 add it for me?
Yes. The folder has a PROMPT.md. Paste it into Cursor, Lovable, v0 or Claude Code.
Every toast closes on a timer. The timer pauses while the stack is fanned out and while a promise is still loading, but there is no "stay until closed" mode. react-toast-notifications defaulted to autoDismiss false, so if you relied on that, pass a long duration (the shim uses ten minutes) or raise duration on the Toaster.
The shim covers addToast, removeToast and removeAllToasts with the options people use most (appearance, autoDismiss, autoDismissTimeout). It doesn't cover toastStack or updateToast, and it ignores ToastProvider props, so move placement and timing to the Toaster yourself.
Accessibility: the toasts sit in a polite live region, error toasts use role="alert", Alt+T moves focus to the newest toast, and Escape or Delete closes the focused one. Reduced-motion users get a fade with no springs.
react-toast-notifications alternative for React 19 · qodesign