Feedback Components
Toast notifications and alert systems for user feedback.
Toast
Toast notifications for temporary messages.
Preview
Live Editor
function ToastDemo() { const [toasts, setToasts] = React.useState([]); const addToast = (type) => { const id = Date.now(); setToasts(prev => [...prev, { id, type }]); setTimeout(() => { setToasts(prev => prev.filter(t => t.id !== id)); }, 3000); }; return ( <div> <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}> <button onClick={() => addToast('default')} style={{ padding: '10px 20px', borderRadius: '8px', background: '#1e293b', color: 'white', border: 'none', cursor: 'pointer', fontWeight: 600, }} > Show Toast </button> <button onClick={() => addToast('success')} style={{ padding: '10px 20px', borderRadius: '8px', background: '#22c55e', color: 'white', border: 'none', cursor: 'pointer', fontWeight: 600, }} > Success Toast </button> <button onClick={() => addToast('error')} style={{ padding: '10px 20px', borderRadius: '8px', background: '#ef4444', color: 'white', border: 'none', cursor: 'pointer', fontWeight: 600, }} > Error Toast </button> </div> <div style={{ position: 'fixed', bottom: '24px', right: '24px', display: 'flex', flexDirection: 'column', gap: '8px', zIndex: 9999, }}> {toasts.map((toast) => ( <div key={toast.id} style={{ padding: '16px 20px', borderRadius: '8px', background: 'white', boxShadow: '0 10px 40px -10px rgba(0,0,0,0.2)', border: '1px solid #e2e8f0', display: 'flex', alignItems: 'center', gap: '12px', minWidth: '300px', animation: 'slideIn 0.3s ease-out', }} > <span style={{ width: '8px', height: '8px', borderRadius: '50%', background: toast.type === 'success' ? '#22c55e' : toast.type === 'error' ? '#ef4444' : '#8b5cf6', }} /> <div> <div style={{ fontWeight: 600, fontSize: '14px' }}> {toast.type === 'success' ? 'Success!' : toast.type === 'error' ? 'Error' : 'Notification'} </div> <div style={{ fontSize: '13px', color: '#64748b' }}> {toast.type === 'success' ? 'Your changes have been saved.' : toast.type === 'error' ? 'Something went wrong.' : 'You have a new message.'} </div> </div> </div> ))} </div> <style>{` @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } `}</style> </div> ); }
Result
Loading...
Usage
import {
Toast,
ToastProvider,
ToastViewport,
ToastTitle,
ToastDescription,
ToastAction,
ToastClose,
useToast,
toast,
Toaster,
} from "@cpod/react";
// 1. Add Toaster to your app root
function App() {
return (
<>
<YourApp />
<Toaster />
</>
);
}
// 2. Use the toast function anywhere
function MyComponent() {
const { toast } = useToast();
return (
<Button
onClick={() => {
toast({
title: "Scheduled: Catch up",
description: "Friday, February 10, 2023 at 5:57 PM",
});
}}
>
Show Toast
</Button>
);
}
// With action button
toast({
title: "Undo",
description: "Message deleted",
action: (
<ToastAction altText="Undo">Undo</ToastAction>
),
});
// Destructive variant
toast({
variant: "destructive",
title: "Error",
description: "Something went wrong.",
});
API
useToast Hook:
const { toast, dismiss, toasts } = useToast();
// Show toast
toast({
title: "Title",
description: "Description",
variant: "default" | "destructive",
action: <ToastAction altText="Action">Action</ToastAction>,
});
// Dismiss specific toast
dismiss(toastId);
// Dismiss all
dismiss();
Toaster Component
The Toaster component renders the toast viewport and should be added to your app root:
import { Toaster } from "@cpod/react";
function App() {
return (
<>
<YourApp />
<Toaster />
</>
);
}
Toaster Props:
| Prop | Type | Default | Description |
|---|---|---|---|
position | top-left | top-right | bottom-left | bottom-right | top-center | bottom-center | bottom-right | Toast position |
duration | number | 5000 | Auto-dismiss duration (ms) |
Sonner
Enhanced toast notifications with the Sonner library.
Preview
Live Editor
function SonnerDemo() { const [toasts, setToasts] = React.useState([]); const showToast = (type) => { const id = Date.now(); const messages = { default: { title: 'Event created', desc: 'Sunday, December 03, 2023 at 9:00 AM' }, success: { title: 'Success', desc: 'Your changes have been saved successfully.' }, error: { title: 'Error', desc: 'There was a problem with your request.' }, loading: { title: 'Loading...', desc: 'Please wait while we process your request.' }, }; setToasts(prev => [...prev, { id, type, ...messages[type] }]); if (type !== 'loading') { setTimeout(() => { setToasts(prev => prev.filter(t => t.id !== id)); }, 4000); } }; return ( <div> <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}> {['default', 'success', 'error', 'loading'].map((type) => ( <button key={type} onClick={() => showToast(type)} style={{ padding: '10px 20px', borderRadius: '8px', background: type === 'success' ? '#22c55e' : type === 'error' ? '#ef4444' : type === 'loading' ? '#f59e0b' : '#8b5cf6', color: 'white', border: 'none', cursor: 'pointer', fontWeight: 600, textTransform: 'capitalize', }} > {type} </button> ))} </div> <div style={{ position: 'fixed', bottom: '24px', right: '24px', display: 'flex', flexDirection: 'column', gap: '8px', zIndex: 9999, }}> {toasts.map((toast) => ( <div key={toast.id} style={{ padding: '16px', borderRadius: '12px', background: 'white', boxShadow: '0 10px 40px -10px rgba(0,0,0,0.2)', border: '1px solid #e2e8f0', display: 'flex', alignItems: 'flex-start', gap: '12px', minWidth: '320px', animation: 'slideIn 0.3s ease-out', }} > <div style={{ width: '24px', height: '24px', borderRadius: '50%', background: toast.type === 'success' ? '#dcfce7' : toast.type === 'error' ? '#fee2e2' : toast.type === 'loading' ? '#fef3c7' : '#f3e8ff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '12px', flexShrink: 0, }}> {toast.type === 'success' ? '✓' : toast.type === 'error' ? '✕' : toast.type === 'loading' ? '◌' : '🔔'} </div> <div style={{ flex: 1 }}> <div style={{ fontWeight: 600, fontSize: '14px', marginBottom: '2px' }}> {toast.title} </div> <div style={{ fontSize: '13px', color: '#64748b' }}> {toast.desc} </div> </div> <button onClick={() => setToasts(prev => prev.filter(t => t.id !== toast.id))} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#94a3b8', fontSize: '16px', padding: 0, }} > × </button> </div> ))} </div> <style>{` @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } `}</style> </div> ); }
Result
Loading...
Usage
import { Sonner } from "@cpod/react";
import { toast } from "sonner";
// Add Sonner to your app root
function App() {
return (
<ThemeProvider>
<YourApp />
<Sonner />
</ThemeProvider>
);
}
// Use toast() from sonner package
function MyComponent() {
return (
<Button onClick={() => toast("Event has been created")}>
Show Toast
</Button>
);
}
// Different types
toast.success("Your changes have been saved");
toast.error("Something went wrong");
toast.loading("Loading...");
toast.info("Did you know?");
// With description
toast("Event created", {
description: "Sunday, December 03, 2023 at 9:00 AM",
});
// With action
toast("Deleted", {
action: {
label: "Undo",
onClick: () => console.log("Undo"),
},
});
// Promise toast
toast.promise(saveData(), {
loading: "Saving...",
success: "Saved!",
error: "Error saving data",
});
Props
Sonner Component:
| Prop | Type | Default | Description |
|---|---|---|---|
position | string | bottom-right | Toast position |
expand | boolean | false | Expand toasts by default |
richColors | boolean | false | Use rich colors for types |
duration | number | 4000 | Default duration in ms |
closeButton | boolean | false | Show close button |
Why Sonner?
Sonner provides additional features over the basic Toast:
- Promise handling - Automatic loading/success/error states
- Rich animations - Smooth enter/exit animations
- Theme integration - Works with ThemeProvider
- Stacking - Multiple toasts stack elegantly
- Positioning - Multiple position options
- Actions - Built-in action buttons
Best Practices
1. Choose the Right Component
| Use Case | Component |
|---|---|
| Simple notifications | Toast |
| Rich notifications with actions | Sonner |
| Async operation feedback | Sonner with promises |
| Error messages | Both with destructive variant |
2. Toast Duration Guidelines
| Type | Duration | Reason |
|---|---|---|
| Success | 3-4 seconds | Quick confirmation |
| Info | 4-5 seconds | Time to read |
| Error | 5-7 seconds | Important, needs attention |
| Loading | Until complete | User needs to wait |
3. Action Buttons
// Good - Clear action
toast({
title: "Message deleted",
action: <ToastAction altText="Undo delete">Undo</ToastAction>,
});
// Avoid - Too many actions
toast({
title: "New message",
action: (
<>
<button>Reply</button>
<button>Archive</button>
<button>Delete</button>
</>
),
});
4. Error Handling
try {
await saveData();
toast.success("Saved successfully");
} catch (error) {
toast.error("Failed to save", {
description: error.message,
action: {
label: "Retry",
onClick: () => saveData(),
},
});
}