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 toastsconst 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
Prop | Type | Default | Description |
---|---|---|---|
message | string | ReactNode | required | Toast content/message |
type | 'success' | 'error' | 'warning' | 'info' | 'info' | Toast type affecting color and icon |
duration | number | 5000 | Auto-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 |
onClose | function | undefined | Callback when toast is closed |
showIcon | boolean | true | Whether to show type icon |
className | string | '' | Additional CSS classes |
ToastContainer Component
Prop | Type | Default | Description |
---|---|---|---|
toasts | Array<ToastObject> | [] | Array of toast configurations |
position | string | '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 toastsconst 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 componentconst 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:
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
- Choose appropriate types - Match toast type with message importance
- Use reasonable durations - 3-5 seconds for most messages
- Keep messages concise - Toast space is limited, be brief
- Don’t overuse - Avoid showing too many toasts simultaneously
- Position consistently - Use the same position throughout your app
- Provide manual close - Always allow users to dismiss toasts
- 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