Skip to content

Toast

A flexible toast notification system for displaying temporary messages, alerts, and feedback. Built with React and styled with Tailwind CSS with multiple positioning options and auto-dismiss functionality.

Component Code

import React, { useState, useEffect } from "react";
import {
CheckCircleIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
XCircleIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
const Toast = ({
message,
type = "info",
duration = 5000,
position = "top-right",
onClose,
showIcon = true,
className = "",
}) => {
const [isVisible, setIsVisible] = useState(true);
const [isEntering, setIsEntering] = useState(true);
useEffect(() => {
// Entry animation
const enterTimer = setTimeout(() => setIsEntering(false), 100);
// Auto dismiss
if (duration > 0) {
const dismissTimer = setTimeout(() => {
handleClose();
}, duration);
return () => {
clearTimeout(enterTimer);
clearTimeout(dismissTimer);
};
}
return () => clearTimeout(enterTimer);
}, [duration]);
const handleClose = () => {
setIsVisible(false);
setTimeout(() => {
onClose?.();
}, 300);
};
const getPositionStyles = () => {
const positions = {
"top-left": "top-4 left-4",
"top-center": "top-4 left-1/2 transform -translate-x-1/2",
"top-right": "top-4 right-4",
"bottom-left": "bottom-4 left-4",
"bottom-center": "bottom-4 left-1/2 transform -translate-x-1/2",
"bottom-right": "bottom-4 right-4",
};
return positions[position] || positions["top-right"];
};
const getTypeStyles = () => {
switch (type) {
case "success":
return "bg-green-50 border-green-200 text-green-800";
case "error":
return "bg-red-50 border-red-200 text-red-800";
case "warning":
return "bg-yellow-50 border-yellow-200 text-yellow-800";
case "info":
default:
return "bg-blue-50 border-blue-200 text-blue-800";
}
};
const getIcon = () => {
const iconClass = "w-5 h-5 flex-shrink-0";
switch (type) {
case "success":
return <CheckCircleIcon className={`${iconClass} text-green-500`} />;
case "error":
return <XCircleIcon className={`${iconClass} text-red-500`} />;
case "warning":
return (
<ExclamationTriangleIcon className={`${iconClass} text-yellow-500`} />
);
case "info":
default:
return (
<InformationCircleIcon className={`${iconClass} text-blue-500`} />
);
}
};
const getAnimationStyles = () => {
if (!isVisible) {
return "opacity-0 translate-y-2 scale-95";
}
if (isEntering) {
return "opacity-0 translate-y-2 scale-95";
}
return "opacity-100 translate-y-0 scale-100";
};
return (
<div
className={`
fixed ${getPositionStyles()} z-50 max-w-sm w-full mx-auto
transition-all duration-300 ease-out
${getAnimationStyles()}
`}
>
<div
className={`
${getTypeStyles()}
flex items-start p-4 rounded-lg border shadow-lg
${className}
`}
role="alert"
aria-live="polite"
>
{showIcon && <div className="mr-3 mt-0.5">{getIcon()}</div>}
<div className="flex-1 text-sm font-medium">{message}</div>
<button
onClick={handleClose}
className="ml-3 flex-shrink-0 p-1 rounded-md hover:bg-black hover:bg-opacity-10 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200"
aria-label="Close notification"
>
<XMarkIcon className="w-4 h-4" />
</button>
</div>
</div>
);
};
// Toast Container Component for managing multiple toasts
const ToastContainer = ({ toasts = [], position = "top-right" }) => {
const getContainerStyles = () => {
const positions = {
"top-left": "top-4 left-4",
"top-center": "top-4 left-1/2 transform -translate-x-1/2",
"top-right": "top-4 right-4",
"bottom-left": "bottom-4 left-4",
"bottom-center": "bottom-4 left-1/2 transform -translate-x-1/2",
"bottom-right": "bottom-4 right-4",
};
return positions[position] || positions["top-right"];
};
return (
<div
className={`fixed ${getContainerStyles()} z-50 space-y-3 max-w-sm w-full`}
>
{toasts.map((toast) => (
<Toast key={toast.id} {...toast} />
))}
</div>
);
};
export { Toast, ToastContainer };
export default Toast;

Usage Example

import React, { useState } from "react";
import Toast, { ToastContainer } from "./Toast";
const App = () => {
const [toasts, setToasts] = useState([]);
const addToast = (message, type = "info", options = {}) => {
const id = Date.now() + Math.random();
const newToast = {
id,
message,
type,
onClose: () => removeToast(id),
...options,
};
setToasts((prev) => [...prev, newToast]);
};
const removeToast = (id) => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
};
const clearAllToasts = () => {
setToasts([]);
};
return (
<div className="max-w-4xl mx-auto p-6 space-y-8">
<h1 className="text-3xl font-bold text-gray-900">Toast Notifications</h1>
{/* Demo Buttons */}
<div className="space-y-4">
<h2 className="text-xl font-semibold">Try Different Toast Types</h2>
<div className="flex flex-wrap gap-3">
<button
onClick={() =>
addToast("Operation completed successfully!", "success")
}
className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600"
>
Success Toast
</button>
<button
onClick={() =>
addToast(
"An error occurred while processing your request.",
"error"
)
}
className="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600"
>
Error Toast
</button>
<button
onClick={() =>
addToast(
"Please review your settings before continuing.",
"warning"
)
}
className="px-4 py-2 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600"
>
Warning Toast
</button>
<button
onClick={() =>
addToast(
"New features are now available in your dashboard.",
"info"
)
}
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
>
Info Toast
</button>
</div>
</div>
{/* Position Examples */}
<div className="space-y-4">
<h2 className="text-xl font-semibold">Different Positions</h2>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<button
onClick={() =>
addToast("Top Left Toast", "info", { position: "top-left" })
}
className="px-3 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 text-sm"
>
Top Left
</button>
<button
onClick={() =>
addToast("Top Center Toast", "info", { position: "top-center" })
}
className="px-3 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 text-sm"
>
Top Center
</button>
<button
onClick={() =>
addToast("Top Right Toast", "info", { position: "top-right" })
}
className="px-3 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 text-sm"
>
Top Right
</button>
<button
onClick={() =>
addToast("Bottom Left Toast", "info", { position: "bottom-left" })
}
className="px-3 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 text-sm"
>
Bottom Left
</button>
<button
onClick={() =>
addToast("Bottom Center Toast", "info", {
position: "bottom-center",
})
}
className="px-3 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 text-sm"
>
Bottom Center
</button>
<button
onClick={() =>
addToast("Bottom Right Toast", "info", {
position: "bottom-right",
})
}
className="px-3 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 text-sm"
>
Bottom Right
</button>
</div>
</div>
{/* Duration Examples */}
<div className="space-y-4">
<h2 className="text-xl font-semibold">Custom Duration</h2>
<div className="flex flex-wrap gap-3">
<button
onClick={() =>
addToast("Quick toast (2s)", "success", { duration: 2000 })
}
className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600"
>
2 Second Toast
</button>
<button
onClick={() =>
addToast("Persistent toast (no auto-dismiss)", "warning", {
duration: 0,
})
}
className="px-4 py-2 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600"
>
Persistent Toast
</button>
<button
onClick={() =>
addToast("Long toast (10s)", "info", { duration: 10000 })
}
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
>
10 Second Toast
</button>
</div>
</div>
{/* Clear All Button */}
{toasts.length > 0 && (
<div className="pt-4 border-t">
<button
onClick={clearAllToasts}
className="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700"
>
Clear All Toasts ({toasts.length})
</button>
</div>
)}
{/* Toast Container */}
<ToastContainer toasts={toasts} position="top-right" />
</div>
);
};
export default App;

Props

Toast Component

PropTypeDefaultDescription
messagestring | ReactNoderequiredToast content/message
type'success' | 'error' | 'warning' | 'info''info'Toast type affecting color and icon
durationnumber5000Auto-dismiss time in ms (0 = no auto-dismiss)
position'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right''top-right'Screen position
onClosefunctionundefinedCallback when toast is closed
showIconbooleantrueWhether to show type icon
classNamestring''Additional CSS classes

ToastContainer Component

PropTypeDefaultDescription
toastsArray<ToastObject>[]Array of toast configurations
positionstring'top-right'Position for all toasts in container

Features

  • Multiple Types: Success, error, warning, info with distinct styling
  • Flexible Positioning: 6 different screen positions
  • Auto-dismiss: Configurable duration with smooth animations
  • Manual Close: Click to dismiss with close button
  • Smooth Animations: CSS transitions for enter/exit effects
  • Stacking Support: Multiple toasts with proper spacing
  • Accessibility: ARIA attributes and keyboard navigation

Toast Types

  • Success: Green theme with checkmark - for confirmations and successful operations
  • Error: Red theme with X icon - for failures and critical issues
  • Warning: Yellow theme with triangle - for cautions and important notices
  • Info: Blue theme with info icon - for general information and tips

Common Patterns

API Response Handling

const handleApiCall = async () => {
try {
await apiCall();
addToast("Data saved successfully!", "success");
} catch (error) {
addToast("Failed to save data. Please try again.", "error");
}
};

Form Submission

const handleSubmit = async (formData) => {
addToast("Submitting form...", "info", { duration: 0 });
try {
await submitForm(formData);
removeToast(loadingToastId);
addToast("Form submitted successfully!", "success");
} catch (error) {
removeToast(loadingToastId);
addToast("Submission failed. Please try again.", "error");
}
};

User Actions

const handleCopy = () => {
navigator.clipboard.writeText(textToCopy);
addToast("Copied to clipboard!", "success", { duration: 2000 });
};
const handleDelete = () => {
deleteItem();
addToast("Item deleted successfully", "success");
};

Advanced Usage

Toast Manager Hook

// Custom hook for managing toasts
const useToast = () => {
const [toasts, setToasts] = useState([]);
const addToast = (message, type = "info", options = {}) => {
const id = Date.now() + Math.random();
const toast = {
id,
message,
type,
onClose: () => removeToast(id),
...options,
};
setToasts((prev) => [...prev, toast]);
return id;
};
const removeToast = (id) => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
};
const clearAll = () => {
setToasts([]);
};
return { toasts, addToast, removeToast, clearAll };
};
// Usage in component
const MyComponent = () => {
const { toasts, addToast, clearAll } = useToast();
return (
<div>
<button onClick={() => addToast("Hello World!", "success")}>
Show Toast
</button>
<ToastContainer toasts={toasts} />
</div>
);
};

Different Positions Example

// Bottom-left positioning
<Toast
message="Settings saved"
type="success"
position="bottom-left"
duration={3000}
/>
// Top-center for important announcements
<Toast
message="System maintenance in 5 minutes"
type="warning"
position="top-center"
duration={0} // Persistent
/>

Custom Styling

// Dark theme toast
<Toast
message="Dark theme enabled"
type="info"
className="bg-gray-800 border-gray-700 text-gray-100"
/>
// Large toast
<Toast
message="Important announcement"
type="warning"
className="text-base p-6 max-w-md"
/>

Installation

Install Heroicons for the toast icons:

Terminal window
npm install @heroicons/react

Positioning Options

  • Top: top-left, top-center, top-right
  • Bottom: bottom-left, bottom-center, bottom-right

Each position is fixed and appears in the appropriate corner or center of the screen.

Best Practices

  1. Choose appropriate types - Match toast type with message importance
  2. Use reasonable durations - 3-5 seconds for most messages
  3. Keep messages concise - Toast space is limited, be brief
  4. Don’t overuse - Avoid showing too many toasts simultaneously
  5. Position consistently - Use the same position throughout your app
  6. Provide manual close - Always allow users to dismiss toasts
  7. Test on mobile - Ensure toasts don’t interfere with mobile navigation

Accessibility Features

  • ARIA Live Regions: Screen readers announce new toasts
  • Keyboard Navigation: Close button is keyboard accessible
  • Focus Management: Doesn’t interfere with page focus flow
  • High Contrast: Works well with accessibility themes
  • Screen Reader Labels: Descriptive close button labels

Notes

  • Toasts are positioned fixed and appear above all other content
  • Multiple toasts stack vertically with proper spacing
  • Smooth CSS animations for professional appearance
  • No external dependencies except Heroicons
  • Easy to integrate with existing state management systems