DEV Community

Cover image for I Built a Toast Library for React & Next.js — Here’s How It Works 🍞
Masaud Ahmod
Masaud Ahmod

Posted on AI-assisted

I Built a Toast Library for React & Next.js — Here’s How It Works 🍞

If you’ve worked on modern web applications, you’ve probably used toast notifications.

“Saved successfully.”

“Something went wrong.”

“Uploading…”

“File deleted — Undo”

They look simple from the outside, but toast notifications become surprisingly useful once an application has many user interactions.

I wanted something that was simple to use, lightweight, modern-looking, and flexible enough for common React and Next.js projects.

That’s why I built your-toast — a small toast notification library for React and Next.js with a modern glass-style UI and a simple API.


🍞 Why Your Toast?

In most applications, I don't need a huge notification system.

Usually, I just need something like:

toast.success("Profile updated!");
Enter fullscreen mode Exit fullscreen mode

The goal behind your-toast is exactly that:

Keep the API simple while giving developers enough control when they need it.

It supports common notification patterns without requiring complicated setup.


🚀 How It Works

The basic architecture is simple.

There are three main parts:

Toast API
   ↓
Toast Store
   ↓
YourToastProvider
   ↓
UI
Enter fullscreen mode Exit fullscreen mode

When you call:

toast.success("Saved successfully!");
Enter fullscreen mode Exit fullscreen mode

the toast API creates a toast object and sends it to an internal store.

The store keeps track of active toasts.

Then YourToastProvider listens to the store and renders the current toast notifications.

So developers don't need to manually manage toast state inside every component.


⚛️ React Usage

For a React/Vite application, you can mount the provider once:

import { YourToastProvider, toast } from "your-toast";

function App() {
  return (
    <>
      <YourToastProvider />

      <button onClick={() => toast.success("Saved!")}>
        Save
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

After that, you can trigger notifications wherever you need them.

toast("Hello!");
toast.success("Saved successfully!");
toast.error("Something went wrong!");
toast.warning("Please check your input.");
toast.info("New update available.");
toast.loading("Uploading...");
Enter fullscreen mode Exit fullscreen mode

The API stays intentionally small and readable.


▲ Next.js App Router

One thing I wanted to handle properly was the difference between normal React applications and Next.js App Router.

In Next.js, the root layout can remain a Server Component.

Instead of making the entire layout client-side, your-toast provides a dedicated client entry:

import { YourToastProvider } from "your-toast/provider";
Enter fullscreen mode Exit fullscreen mode

Then:

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}

        <YourToastProvider />
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

You don't need to add "use client" to the root layout.

Then from a Client Component:

"use client";

import { toast } from "your-toast";

export default function Button() {
  return (
    <button onClick={() => toast.success("Done!")}>
      Click me
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

This keeps the Client Component boundary explicit without turning the whole application into a client component.

That was one of the architectural details I specifically wanted to get right.


⚡ Promise-Based Toasts

One of my favorite features is toast.promise().

Instead of manually handling loading, success, and error states:

toast.loading("Loading...");

// do something

toast.success("Completed!");
Enter fullscreen mode Exit fullscreen mode

you can connect the toast directly to a promise:

toast.promise(fetchData(), {
  loading: "Loading...",
  success: "Data loaded!",
  error: "Something went wrong.",
});
Enter fullscreen mode Exit fullscreen mode

It can also work with dynamic results:

toast.promise(fetchData(), {
  loading: "Loading...",
  success: (data) => `Loaded ${data.name}`,
  error: () => "Failed to load data.",
});
Enter fullscreen mode Exit fullscreen mode

This makes async UI feedback much easier to manage.


🔁 More Control When You Need It

Sometimes a notification needs to stay under your control.

For example:

const id = toast.loading("Uploading...");
Enter fullscreen mode Exit fullscreen mode

Then after the upload:

toast.update(id, {
  title: "Upload complete!",
  type: "success",
});
Enter fullscreen mode Exit fullscreen mode

You can also dismiss a specific toast:

toast.dismiss(id);
Enter fullscreen mode Exit fullscreen mode

Or dismiss all active notifications:

toast.dismiss();
Enter fullscreen mode Exit fullscreen mode

You can customize duration and descriptions as well:

toast.success("Profile updated!", {
  description: "Your changes have been saved.",
  duration: 3000,
});
Enter fullscreen mode Exit fullscreen mode

And for actions:

toast("File deleted", {
  action: {
    label: "Undo",
    onClick: () => {
      console.log("Undo clicked");
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

🧩 A Small Library, But With Real Architecture

Although the package is small, I didn't want it to be just a component with some setTimeout() calls.

The library uses an external store to keep toast state separate from the UI.

React's useSyncExternalStore is used to safely subscribe the provider to that store.

The package is also built with:

  • TypeScript
  • ESM
  • CommonJS
  • TypeScript declaration files
  • React 18+
  • React 19
  • Next.js App Router support

The result is a small package that can fit into different React application setups without adding unnecessary dependencies.


🎯 When Would I Use It?

I think your-toast fits especially well when an application needs frequent UI feedback for things like:

  • Form submissions
  • Authentication
  • API requests
  • File uploads
  • CRUD operations
  • Saving settings
  • Delete/undo actions
  • Background operations
  • Success/error feedback

For these common cases, I wanted the experience to be as simple as:

toast.success("Everything is ready!");
Enter fullscreen mode Exit fullscreen mode

No complicated configuration. Just feedback when the user needs it.


🚧 Still Improving

your-toast is still growing.

Some ideas on the roadmap include:

  • Better animations
  • More advanced glass UI
  • Theme system
  • Mobile improvements
  • Progress indicators
  • Swipe-to-dismiss
  • Custom icons
  • Custom React content

So this isn't a “finished forever” library.

I'm building it, using it, and improving it based on real use cases and developer feedback.


⭐ Try It & Help Improve It

If you're working with React or Next.js and need a simple toast library, you can check out your-toast:

NPM:

https://www.npmjs.com/package/your-toast

GitHub:

https://github.com/masaudahmod/your-toast

If you find it useful, a ⭐ on GitHub would mean a lot.

And if you have an idea, find a bug, or think something could be improved, feel free to open an issue or share your suggestion.

Developer feedback is one of the best ways to make an open-source project better.


💭 Final Thought

Building your-toast reminded me that even a small UI feature can involve interesting engineering decisions.

A toast may look like a tiny notification on the screen, but behind that notification there’s state management, rendering, accessibility, package architecture, framework compatibility, and developer experience.

And that's exactly what I enjoyed about building it.

Sometimes, small projects are where you learn the most. 🍞

Top comments (0)