Toast Notifications
Featuredby Dax · tsx · 50 lines
A toast notification system with a hook for easy use anywhere. Slides in from the bottom-right, auto-dismisses after 4 seconds, and stacks neatly when multiple fire at once.
toastnotificationhookradix
50 lines
"use client";
import * as Toast from "@radix-ui/react-toast";
import { createContext, useContext, useState, useCallback } from "react";
import { X } from "lucide-react";
type ToastData = { id: number; title: string; description?: string };
const Ctx = createContext<(t: Omit<ToastData, "id">) => void>(() => {});
export const useToast = () => useContext(Ctx);
let nextId = 0;
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [toasts, setToasts] = useState<ToastData[]>([]);
const toast = useCallback((t: Omit<ToastData, "id">) => {
setToasts((prev) => [...prev, { ...t, id: ++nextId }]);
}, []);
return (
<Ctx.Provider value={toast}>
<Toast.Provider duration={4000}>
{children}
{toasts.map((t) => (
<Toast.Root
key={t.id}
onOpenChange={(open) => {
if (!open) setToasts((p) => p.filter((x) => x.id !== t.id));
}}
className="rounded-xl border border-neutral-200 bg-white p-4 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-bottom-full"
>
<div className="flex items-start gap-3">
<div className="flex-1">
<Toast.Title className="text-sm font-semibold">{t.title}</Toast.Title>
{t.description && (
<Toast.Description className="mt-1 text-xs text-neutral-500">
{t.description}
</Toast.Description>
)}
</div>
<Toast.Close className="text-neutral-400 hover:text-neutral-600">
<X size={14} />
</Toast.Close>
</div>
</Toast.Root>
))}
<Toast.Viewport className="fixed bottom-4 right-4 z-50 flex flex-col gap-2 w-80" />
</Toast.Provider>
</Ctx.Provider>
);
}