A complete rewrite of my open-source React toast library, with a focus on developer experience, accessibility, customization, and reliability.
After a lot of development, refactoring, testing, and quite a few lessons learned along the way, I'm excited to finally release React Elegant Toasts v2.0!
It's an open-source toast notification library for React 18 and 19, built with TypeScript and designed to be easy to use while handling the more complicated details behind the scenes.
Why another React toast library?
That's a fair question.
There are already some excellent toast notification libraries in the React ecosystem. I'm not going to pretend otherwise.
For me, this project started with a simple idea: I wanted to build something that looked good, felt natural to use, and offered a pleasant developer experience.
But as the project grew, I became increasingly interested in everything happening behind the visible notification.
How should timers behave when the browser tab becomes inactive?
What happens when a user interacts with a toast using the keyboard?
How should notifications be announced to screen readers?
How do you handle multiple toast queues without losing notifications?
And how do you make all of this work consistently across different browsers?
Those questions eventually turned a relatively small component project into something much more interesting.
With v2.0, I decided to take what I'd learned and rebuild the library around a more deliberate architecture.
Getting started
One of my main goals was to keep the public API simple.
Install the package:
npm install react-elegant-toasts
Import the stylesheet, render a single Toaster component, and start showing notifications:
import 'react-elegant-toasts/styles.css';
import { Toaster, toast } from 'react-elegant-toasts';
export function App() {
return (
<>
<button
type="button"
onClick={() => toast.success('Changes saved!')}
>
Save changes
</button>
<Toaster />
</>
);
}
That's it.
The library handles rendering, positioning, dismissal, timers, stacking, and animations.
The toast API also works outside React components, so you don't need to pass notification functions through your component tree.
Different types of notifications
React Elegant Toasts supports several built-in toast types:
toast('This is a default notification');
toast.success('Profile updated successfully');
toast.error('Something went wrong');
toast.warning('Your session is about to expire');
toast.info('A new update is available');
toast.loading('Uploading your files...');
You can also add descriptions, actions, custom durations, and different positions.
For example:
toast.success('Message archived', {
description: 'You can find it in your Archive folder.',
duration: 8000,
position: 'bottom-center',
action: {
label: 'Undo',
onClick: () => restoreMessage(),
},
});
There are six available positions, and each position maintains its own stack and queue.
That last part is important. If more notifications arrive than can be displayed, the additional notifications wait for their turn rather than simply disappearing.
Promise-based notifications
One feature I particularly like is promise handling.
Imagine submitting a form or saving something to an API.
Instead of manually managing loading, success, and error notifications, you can use toast.promise:
toast.promise(saveProject(), {
loading: 'Saving project...',
success: project => `Saved ${project.name}!`,
error: error =>
error instanceof Error
? error.message
: 'Something went wrong',
});
The notification starts in a loading state and updates when the promise resolves or rejects.
It's a small feature, but one that makes everyday development a little nicer.
Customization without fighting the library
I wanted customization to be straightforward.
React Elegant Toasts includes light, dark, and system themes, with CSS custom properties for styling.
For example:
<Toaster
position="bottom-right"
theme="dark"
maxVisible={3}
progress
className="my-toaster"
/>
.my-toaster {
--ret-radius: 6px;
--ret-width: 400px;
}
You can also create custom toasts with your own React markup, while the library continues to manage positioning, timers, and dismissal.
My goal was to provide sensible defaults without making developers feel locked into one visual style.
Accessibility was a major focus
One of the areas that received a lot of attention during the rewrite was accessibility.
Toast notifications seem simple, but they introduce some surprisingly difficult questions.
A notification shouldn't unexpectedly steal keyboard focus. Users need enough time to read or interact with it. Important messages need to be communicated to assistive technologies.
In v2.0, I worked on features including:
- Live regions for notification announcements, with assertive announcements for errors.
- Keyboard navigation, including an Alt+T shortcut to reach notifications.
- Pausing dismissal timers during hover, keyboard focus, and page inactivity.
- Support for reduced-motion preferences.
- Customizable labels for assistive technology.
Accessibility is an ongoing process, not a checkbox.
The library has automated accessibility coverage, but I also want to be transparent: real screen-reader testing is still an area where more validation is needed. I'm not claiming conformance to a particular accessibility standard.
If accessibility is something you work with, I'd genuinely appreciate feedback.
You can read more in the accessibility documentation.
A different architecture under the hood
The biggest change in v2 isn't necessarily visible in the notifications themselves.
It's the architecture.
Instead of making React responsible for everything, the library uses an external store to manage toast state, queues, timers, and lifecycle behavior.
The React layer is responsible for subscribing to that state and rendering the notifications.
That separation made it easier to reason about important behaviors independently of the UI.
It also helped make the library more testable.
The public API stays intentionally small:
import { Toaster, toast } from 'react-elegant-toasts';
And the package has zero runtime dependencies, with React and React DOM declared as peer dependencies.
Version 2 is ESM-only and includes TypeScript declarations.
It also supports server rendering and the Next.js App Router, including a client boundary for the toast functionality.
Testing across browsers
Another big part of the v2 development process was testing.
I didn't want to rely entirely on component tests running in a simulated DOM.
Some behaviors, particularly animations, gestures, focus handling, and CSS transitions, need to be tested in real browser engines.
The project uses Playwright to test against:
- Chromium
- Firefox
- WebKit
Alongside the browser tests, there are unit, component, TypeScript, and accessibility-oriented tests.
And I learned an interesting lesson along the way: sometimes a failing animation test isn't caused by a broken animation.
We ran into a WebKit issue where animation frames weren't always delivered frequently enough for a test to observe intermediate positions. The test could fail even though the transition itself was behaving correctly.
Fixing that required looking more closely at transition evidence rather than assuming every animation would produce conveniently timed frames.
It's the kind of detail you rarely think about when you first decide to build a toast component!
What's different about v2.0?
Version 2.0 is a complete rewrite rather than a small update to the original implementation.
Some of the highlights include:
- A redesigned public API.
- An external store for notification state and lifecycle management.
- Independent notification queues for all six positions.
- Custom toasts and promise notifications.
- More deliberate timer and pause behavior.
- Improved accessibility-related functionality.
- CSS-based themes and customization.
- Better browser and package-level testing.
- Support for React 18 and 19.
If you're already using an older 0.x version, please note that v2 introduces breaking changes.
There's a migration guide to help with the transition.
What's next?
Releasing v2.0 feels like a big milestone, but I don't consider the project finished.
I'd like to keep improving the library based on real-world use, bug reports, accessibility feedback, and ideas from other developers.
I've also added contribution guidelines, issue templates, and GitHub Discussions to make it easier for people to get involved.
The project is open source under the MIT license, and contributions are welcome.
Give it a try!
If you're building something with React and need toast notifications, I'd love for you to give React Elegant Toasts a try.
The easiest way to see what it can do is to explore the live demo:
👉 Try React Elegant Toasts v2.0
You can also find it here:
- GitHub: https://github.com/ahlgren1234/react-elegant-toasts
- npm: https://www.npmjs.com/package/react-elegant-toasts
- Discussions: https://github.com/ahlgren1234/react-elegant-toasts/discussions
And if you find it useful, consider giving the repository a star on GitHub. ⭐
Most importantly, I'd love to hear what you think.
What features do you consider essential in a React toast notification library?
Are there any accessibility or usability details you'd like to see handled differently?
Thanks for reading, and happy coding!
Peter Anyawantana
Tags: react typescript opensource webdev
Top comments (0)