Sonner
Overlay & FeedbackStacked toast notification system.
Preview
Stacked toast notification system. Wrap your app in <SonnerProvider> and use useSonner() to trigger toasts.
Usage
example.jsx
import { SonnerProvider } from "@/components/ui/sonner";
export default function Example() {
return <SonnerProvider />;
}Source Code
Copy this file into components/ui/sonner.jsx in your project.
sonner.jsx
"use client";
import { createContext, useContext, useState, useCallback } from "react";
import { cn } from "@/lib/utils";
const SonnerContext = createContext(null);
export function SonnerProvider({ children, position = "bottom-right" }) {
const [toasts, setToasts] = useState([]);
const toast = useCallback((message, opts = {}) => {
const id = Date.now();
setToasts((p) => [...p, { id, message, type: opts.type || "default", description: opts.description }]);
setTimeout(() => setToasts((p) => p.filter((t) => t.id !== id)), opts.duration || 4000);
}, []);
const posClass = {
"bottom-right": "bottom-4 right-4",
"bottom-left": "bottom-4 left-4",
"top-right": "top-4 right-4",
"top-left": "top-4 left-4",
"top-center": "top-4 left-1/2 -translate-x-1/2",
"bottom-center": "bottom-4 left-1/2 -translate-x-1/2",
}[position];
return (
<SonnerContext.Provider value={toast}>
{children}
<div className={cn("fixed z-[100] flex flex-col gap-2", posClass)}>
{toasts.map((t) => (
<div key={t.id} className={cn("flex items-start gap-3 rounded-lg border bg-background px-4 py-3 shadow-lg animate-in slide-in-from-bottom-2",
t.type === "success" && "border-green-500/50 bg-green-50 dark:bg-green-950",
t.type === "error" && "border-red-500/50 bg-red-50 dark:bg-red-950",
t.type === "warning" && "border-yellow-500/50 bg-yellow-50 dark:bg-yellow-950"
)}>
{t.type === "success" && <span className="text-green-500">✓</span>}
{t.type === "error" && <span className="text-red-500">✕</span>}
{t.type === "warning" && <span className="text-yellow-500">⚠</span>}
<div>
<div className="text-sm font-medium">{t.message}</div>
{t.description && <div className="text-xs text-muted-foreground">{t.description}</div>}
</div>
</div>
))}
</div>
</SonnerContext.Provider>
);
}
export function useSonner() {
const toast = useContext(SonnerContext);
return {
toast,
success: (msg, opts) => toast?.(msg, { ...opts, type: "success" }),
error: (msg, opts) => toast?.(msg, { ...opts, type: "error" }),
warning: (msg, opts) => toast?.(msg, { ...opts, type: "warning" }),
};
}
Quick Install
Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.
npm install clsx tailwind-merge