A modern, fully customizable toast notification library for React and Next.js applications.
- π¨ Beautiful & Modern β Clean design with smooth animations
- π Dark / Light / System β Automatic theme support out of the box
- π― Fully Customizable β Positions, durations, colors, icons, and actions
- π¦ Lightweight β Minimal bundle size with only Zustand as a dependency
- β‘ TypeScript β 100% type-safe with full IntelliSense support
- π Next.js Ready β Works seamlessly with Server Components and the App Router
- π± Responsive β Optimized for all devices and screen sizes
- βΏ Accessible β ARIA-friendly with keyboard navigation support
- π Loading States β Built-in loading toasts with update capabilities
- π Multiple Positions β 6 different positions
- β±οΈ Progress Bar β Visual timer indicator with pause-on-hover support
- πͺ Smooth Animations β Elegant enter and exit animations
- π¨ CSS Isolated β Pure CSS with a
z-toast-*prefix, so it won't conflict with your styles
npm install @zyther/react-toastifyyarn add @zyther/react-toastifypnpm add @zyther/react-toastifyImportant
Since ToastProvider uses client-side hooks (useEffect, useState), you need to wrap it in a Client Component before using it in your Server Component layout.
Create app/components/ClientWrapper.tsx:
'use client';
import { ToastProvider } from "@zyther/react-toastify";
import "@zyther/react-toastify/styles";
interface ClientWrapperProps {
children: React.ReactNode;
}
export function ClientWrapper({ children }: ClientWrapperProps) {
return (
<ToastProvider
defaultPosition="bottom-right"
defaultDuration={4000}
maxToasts={5}
theme="system"
>
{children}
</ToastProvider>
);
}app/layout.tsx (Server Component):
import { ClientWrapper } from "@/components/ClientWrapper";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<ClientWrapper>
{children}
</ClientWrapper>
</body>
</html>
);
}Important
The useToast hook must be used in Client Components with the "use client" directive.
"use client";
import { useToast } from "@zyther/react-toastify";
export default function MyComponent() {
const toast = useToast();
const handleClick = () => {
toast.success("Operation completed successfully! π", {
title: "Success",
duration: 4000,
});
};
return (
<button onClick={handleClick}>
Show Toast
</button>
);
}"use client";
import { useToast } from "@zyther/react-toastify";
function Demo() {
const toast = useToast();
// Success toast
toast.success("Data saved successfully!");
// Error toast with an action button
toast.error("Failed to save data", {
title: "Error",
action: {
label: "Retry",
onClick: () => console.log("Retrying..."),
},
});
// Warning toast
toast.warning("Please check your input fields");
// Info toast
toast.info("New version available", {
duration: 5000,
});
// Loading toast with update
const id = toast.loading("Processing your request...");
setTimeout(() => {
toast.updateToast(id, {
type: "success",
message: "Request completed!",
duration: 3000,
});
}, 2000);
}toast.success("Top right!", {
position: "top-right",
});
toast.info("Top left!", {
position: "top-left",
});
toast.warning("Top center!", {
position: "top-center",
});
toast.error("Bottom right!", {
position: "bottom-right",
});
toast.success("Bottom left!", {
position: "bottom-left",
});
toast.info("Bottom center!", {
position: "bottom-center",
});Disable automatic dismissal:
toast.info("This stays until dismissed", {
duration: 0,
});Custom duration in milliseconds:
toast.success("Short toast", {
duration: 2000,
});
toast.success("Long toast", {
duration: 8000,
});toast.error("Connection lost", {
title: "Network Error",
duration: 5000,
action: {
label: "Reconnect",
onClick: () => {
console.log("Reconnecting...");
},
},
onClose: () => {
console.log("Toast closed");
},
});const toast = useToast();
// Dismiss a specific toast by ID
const id = toast.info("Hello!");
toast.dismissToast(id);
// Dismiss all active toasts
toast.dismissAll();| Prop | Type | Default | Description |
|---|---|---|---|
defaultPosition |
ToastPosition |
"bottom-right" |
Default position for all toasts |
defaultDuration |
number |
4000 |
Default duration in milliseconds. 0 disables auto-dismiss |
maxToasts |
number |
5 |
Maximum number of visible toasts |
theme |
"light" | "dark" | "system" |
"system" |
Theme mode |
type ToastPosition =
| "top-right"
| "top-left"
| "top-center"
| "bottom-right"
| "bottom-left"
| "bottom-center";interface UseToastReturn {
showToast: (
message: string,
options?: ToastOptions
) => string;
success: (
message: string,
options?: Omit<ToastOptions, "type">
) => string;
error: (
message: string,
options?: Omit<ToastOptions, "type">
) => string;
warning: (
message: string,
options?: Omit<ToastOptions, "type">
) => string;
info: (
message: string,
options?: Omit<ToastOptions, "type">
) => string;
loading: (
message: string,
options?: Omit<ToastOptions, "type">
) => string;
updateToast: (
id: string,
updates: Partial<Toast>
) => void;
dismissToast: (id: string) => void;
dismissAll: () => void;
}| Option | Type | Default | Description |
|---|---|---|---|
type |
ToastType |
"info" |
Toast type |
title |
string |
undefined |
Optional title |
duration |
number |
4000 |
Duration in milliseconds. 0 disables auto-dismiss |
position |
ToastPosition |
"bottom-right" |
Toast position |
icon |
React.ReactNode |
undefined |
Custom icon |
action |
{ label: string; onClick: () => void } |
undefined |
Action button |
onClose |
() => void |
undefined |
Callback invoked when the toast closes |
The library uses pure CSS with the z-toast-* prefix to avoid conflicts with your application's styles. No Tailwind CSS is required.
You can override the default styles in your own CSS:
/* Override toast item styles */
.z-toast-item {
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}
/* Override success toast */
.z-toast-success {
background-color: #dcfce7;
border-color: #22c55e;
}toast.info("Custom icon!", {
icon: <CustomIcon />,
});The library is fully typed and provides complete TypeScript support.
You can import the available types directly from the package:
import type {
Toast,
ToastOptions,
ToastType,
ToastPosition,
ToastProviderProps,
} from "@zyther/react-toastify";// app/components/ClientWrapper.tsx
'use client';
import { ToastProvider } from "@zyther/react-toastify";
import "@zyther/react-toastify/styles";
export function ClientWrapper({ children }) {
return <ToastProvider>{children}</ToastProvider>;
}// app/layout.tsx
import { ClientWrapper } from "@/components/ClientWrapper";
export default function RootLayout({ children }) {
return (
<html>
<body>
<ClientWrapper>{children}</ClientWrapper>
</body>
</html>
);
}// app/page.tsx
'use client';
import { useToast } from "@zyther/react-toastify";
export default function Page() {
const toast = useToast();
return (
<button onClick={() => toast.success("Hello!")}>
Click me
</button>
);
}Store toast IDs when you need to update or dismiss a specific toast later:
const id = toast.loading("Processing...");
// Later
toast.updateToast(id, {
type: "success",
message: "Done!",
});Use duration: 0 for important messages that require the user to manually dismiss them.
Keep action callbacks simple and avoid placing heavy business logic directly inside the toast action.
Solution: Create a Client Wrapper component as shown in the Quick Start section. Never import ToastProvider directly in a Server Component.
// β Wrong - Direct import in Server Component
// app/layout.tsx
import { ToastProvider } from "@zyther/react-toastify";
// β
Correct - Use Client Wrapper
// app/components/ClientWrapper.tsx
'use client';
import { ToastProvider } from "@zyther/react-toastify";Solution: Import the styles once in your Client Wrapper:
import "@zyther/react-toastify/styles";Solution: Add the "use client" directive to your component:
"use client";
import { useToast } from "@zyther/react-toastify";- Minimal Bundle β Approximately 6 KB gzipped
- Optimized Renders β Uses Zustand for efficient state management
- Memoized Components β Toast items are memoized for improved performance
- Lazy Rendering β Only renders toast content when needed
- No Dependencies β Only Zustand, no other external libraries
Contributions are welcome!
-
Fork the repository.
-
Create your feature branch:
git checkout -b feature/amazing-feature
-
Commit your changes:
git commit -m "Add some amazing feature" -
Push to the branch:
git push origin feature/amazing-feature
-
Open a Pull Request.
See CHANGELOG.md for the version history.
Found a bug or unexpected behavior?
Please report it through GitHub Issues with:
- A clear description of the problem
- Steps to reproduce it
- Your React and Next.js versions
- Your browser and operating system
- Any relevant error messages or screenshots
MIT Β© zytherdev