Components
Notification Toast
Notification Toast
Animated toast notifications with progress bars, auto-dismiss, and contextual actions.
Task Completed
Your file has been uploaded successfully.
Installation
1
Install the packages
npm i motion clsx tailwind-merge2
Add util file
lib/util.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
3
Copy and paste the following code into your project
notification-toast.tsx
"use client";
import { motion, AnimatePresence } from "motion/react";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
import { FiCheck, FiX, FiAlertTriangle, FiInfo } from "react-icons/fi";
type NotificationToastProps = {
message?: string;
type?: "success" | "error" | "warning" | "info";
duration?: number;
onClose?: () => void;
};
const NotificationToast = ({
message = "Notification message",
type = "info",
duration = 5000,
onClose = () => {},
}: NotificationToastProps) => {
const [isVisible, setIsVisible] = useState(true);
const [progress, setProgress] = useState(100);
useEffect(() => {
const interval = setInterval(() => {
setProgress((prev) => {
if (prev <= 0) {
setIsVisible(false);
onClose();
clearInterval(interval);
return 0;
}
return prev - (100 / (duration / 100));
});
}, 100);
return () => clearInterval(interval);
}, [duration, onClose]);
const icons = {
success: FiCheck,
error: FiX,
warning: FiAlertTriangle,
info: FiInfo,
};
const colors = {
success: "bg-green-500 border-green-500",
error: "bg-red-500 border-red-500",
warning: "bg-yellow-500 border-yellow-500",
info: "bg-blue-500 border-blue-500",
};
const Icon = icons[type];
return (
<AnimatePresence>
{isVisible && (
<motion.div
className={cn(
"fixed top-4 right-4 z-50 max-w-sm w-full",
"bg-white dark:bg-neutral-900 border rounded-lg shadow-lg overflow-hidden"
)}
initial={{ opacity: 0, x: 300, scale: 0.8 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
exit={{ opacity: 0, x: 300, scale: 0.8 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
{/* Progress bar */}
<motion.div
className={cn("h-1", colors[type].split(" ")[0])}
initial={{ width: "100%" }}
animate={{ width: `${progress}%` }}
transition={{ duration: 0.1 }}
/>
<div className="p-4">
<div className="flex items-start gap-3">
<motion.div
className={cn(
"flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-white",
colors[type]
)}
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 0.2, duration: 0.3, ease: "easeOut" }}
>
<Icon className="w-4 h-4" />
</motion.div>
<div className="flex-1">
<motion.p
className="text-sm text-neutral-900 dark:text-white font-medium"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.3 }}
>
{message}
</motion.p>
</div>
<motion.button
className="flex-shrink-0 text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-200 transition-colors"
onClick={() => {
setIsVisible(false);
onClose();
}}
whileTap={{ scale: 0.9 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4, duration: 0.3 }}
>
<FiX className="w-4 h-4" />
</motion.button>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
);
};
export default NotificationToast;"use client";
import { motion, AnimatePresence } from "motion/react";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
import { FiCheck, FiX, FiAlertTriangle, FiInfo } from "react-icons/fi";
type NotificationToastProps = {
message?: string;
type?: "success" | "error" | "warning" | "info";
duration?: number;
onClose?: () => void;
};
const NotificationToast = ({
message = "Notification message",
type = "info",
duration = 5000,
onClose = () => {},
}: NotificationToastProps) => {
const [isVisible, setIsVisible] = useState(true);
const [progress, setProgress] = useState(100);
useEffect(() => {
const interval = setInterval(() => {
setProgress((prev) => {
if (prev <= 0) {
setIsVisible(false);
onClose();
clearInterval(interval);
return 0;
}
return prev - (100 / (duration / 100));
});
}, 100);
return () => clearInterval(interval);
}, [duration, onClose]);
const icons = {
success: FiCheck,
error: FiX,
warning: FiAlertTriangle,
info: FiInfo,
};
const colors = {
success: "bg-green-500 border-green-500",
error: "bg-red-500 border-red-500",
warning: "bg-yellow-500 border-yellow-500",
info: "bg-blue-500 border-blue-500",
};
const Icon = icons[type];
return (
<AnimatePresence>
{isVisible && (
<motion.div
className={cn(
"fixed top-4 right-4 z-50 max-w-sm w-full",
"bg-white dark:bg-neutral-900 border rounded-lg shadow-lg overflow-hidden"
)}
initial={{ opacity: 0, x: 300, scale: 0.8 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
exit={{ opacity: 0, x: 300, scale: 0.8 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
{/* Progress bar */}
<motion.div
className={cn("h-1", colors[type].split(" ")[0])}
initial={{ width: "100%" }}
animate={{ width: `${progress}%` }}
transition={{ duration: 0.1 }}
/>
<div className="p-4">
<div className="flex items-start gap-3">
<motion.div
className={cn(
"flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-white",
colors[type]
)}
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 0.2, duration: 0.3, ease: "easeOut" }}
>
<Icon className="w-4 h-4" />
</motion.div>
<div className="flex-1">
<motion.p
className="text-sm text-neutral-900 dark:text-white font-medium"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.3 }}
>
{message}
</motion.p>
</div>
<motion.button
className="flex-shrink-0 text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-200 transition-colors"
onClick={() => {
setIsVisible(false);
onClose();
}}
whileTap={{ scale: 0.9 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4, duration: 0.3 }}
>
<FiX className="w-4 h-4" />
</motion.button>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
);
};
export default NotificationToast;4
Update the import paths to match your project setup
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| message | string | Notification message | The message to display in the toast. |
| type | string | info | The type of toast (success, error, warning, info). |
| duration | number | 5000 | Duration in milliseconds before auto-dismiss. |