Skip to content

Conversation

@Erik-Koning
Copy link

Problem

Some web-apps have critical messages that need to be seen, or a message that may be toasted during a page navigation or refresh. This PR adds message durability so certain toasts can be restored after a page refresh.

Summary

  • Add persistent option to toast configuration
  • Implement localStorage-based persistence with SSR safety
  • Normalize sonner id upon restoration with counter synchronization moved into class
  • staggered animation for toast restoration
  • customizable storageKey prop to Toaster component

Usage

Basic:

toast('Persistent Toast', { persistent: true })} 

Works with evolving toasts, will update persistence if the toast updates:

toast.promise(
  new Promise((resolve) => {
    setTimeout(() => {
      resolve({ name: 'Sonner' });
    }, 2000);
  }),
  {
    loading: 'Loading...',
    success: (data: any) => ({
      message: `${data.name} toast has been added`,
      description: 'Custom description for the Success state',
    }),
    description: 'Global description',
    persistent: true,
    closeButton: true,
  },
)

Restoration Compatibility

All types of toasts can be used with the persistent property. But, Each toast is stringified and then parsed when restored so obviously any functions or timeouts become disconnected.

Demo

persistent-sonners.mp4

- Add `persistent` option to toast configuration
- Implement localStorage-based persistence with SSR safety
- Add smart ID management with counter synchronization moved into class
- staggered animation for toast restoration
- customizable `storageKey` prop to Toaster component
@vercel
Copy link

vercel bot commented Jul 28, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
sonner ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 28, 2025 2:35am

@emilkowalski
Copy link
Owner

This is nice, but I think this type of behavior should be coded by the consumer rather than be included in the library by default. What do you think?

i++;
});
// Set the toast count to the number of persistent toasts
ToastState.setToastCount(numPersistentToasts);
Copy link
Author

@Erik-Koning Erik-Koning Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setToastCount runs before any addToast timeouts finish

@Erik-Koning
Copy link
Author

Erik-Koning commented Jul 31, 2025

Ya, having it integrated in the library reduces a good amount of boilerplate code and ensures a consistent experience across applications using the library.

But notably, there is one reliability aspect that this PR ensures. On loading persistent toasts we animate only the number of visibleToasts on load so it seems consistent with how the library animates new toasts. While persistent toasts load in we have already set the toastCounter so any new toasts do not overwrite persisted toasts. Since toastCounter cannot be set from outside the library logic, consumer code trying to implement this logic would not be able to guarantee a unique toast ID.

In summary: Both, animating persistent toasts in, and ensuring unique toast ID's is pretty non-obvious for consumer code to handle.

@remcostoeten
Copy link

Very nice. I see what Emil is saying. It is quite comprehensive and feels like a really well-scoped integration for certain niches.

Nonetheless, having toast persist across routing, or whatever, is a very good addition. I would definitely consider it!

Maybe think the API/prop usage through a bit, keeping it as simple as possible. But I’d say +1.

@Fefedu973
Copy link

IMO this should be included in the lib, not implemented by the user.

@Wollace-Buarque
Copy link

I think this could be implemented by the developer, but it would be nice if the library had this too. I believe it's missing an important feature:

  • Option to automatically remove it after being shown N times;

Security problem:

  • Saving this information in LocalStorage without any type of signature/hash to validate the message could allow a malicious person to modify the original message for any purpose…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants