Design SystemComponents
Feedback
Toast, skeleton, and alert patterns
Feedback Components
Components for user feedback and loading states.
Toast (Sonner)
Use Sonner for toast notifications:
import { toast } from "sonner";
// Success
toast.success("Employee created successfully");
// Error
toast.error("Failed to create employee");
// Info
toast.info("Processing your request...");
// Warning
toast.warning("This action cannot be undone");
// With description
toast.success("Employee created", {
description: "John Doe has been added to Engineering",
});
// With action
toast.error("Failed to save", {
action: {
label: "Retry",
onClick: () => handleRetry(),
},
});Toast Setup
Add Toaster to your root layout:
// app/layout.tsx
import { Toaster } from "sonner";
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Toaster
position="bottom-right"
toastOptions={{
classNames: {
toast: "bg-card border-border",
title: "text-foreground",
description: "text-muted-foreground",
actionButton: "bg-primary text-primary-foreground",
},
}}
/>
</body>
</html>
);
}Skeleton
Loading placeholders that match content shape.
// Text skeleton
<Skeleton className="h-4 w-32" />
// Avatar skeleton
<Skeleton className="h-10 w-10 rounded-full" />
// Card skeleton
<Skeleton className="h-32 w-full rounded-2xl" />Skeleton Component
import { cn } from "@/lib/utils";
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
);
}Table Skeleton
<ContentCard>
<div className="space-y-4">
{[1, 2, 3, 4, 5].map((i) => (
<div key={i} className="flex items-center gap-4">
<Skeleton className="h-10 w-10 rounded-full" />
<div className="space-y-2 flex-1">
<Skeleton className="h-4 w-1/3" />
<Skeleton className="h-3 w-1/4" />
</div>
<Skeleton className="h-6 w-16 rounded-full" />
</div>
))}
</div>
</ContentCard>Stats Card Skeleton
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{[1, 2, 3, 4].map((i) => (
<Skeleton key={i} className="h-32 rounded-2xl" />
))}
</div>Alert
Important messages that don't require immediate action.
<Alert>
<Info className="h-4 w-4" />
<AlertTitle>Information</AlertTitle>
<AlertDescription>
Your profile is incomplete. Add your phone number for better security.
</AlertDescription>
</Alert>Alert Variants
// Default
<Alert>
<Info className="h-4 w-4" />
<AlertTitle>Note</AlertTitle>
<AlertDescription>...</AlertDescription>
</Alert>
// Destructive
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>...</AlertDescription>
</Alert>
// Success (custom)
<Alert className="border-success/50 bg-success/10">
<CheckCircle className="h-4 w-4 text-success" />
<AlertTitle className="text-success">Success</AlertTitle>
<AlertDescription>...</AlertDescription>
</Alert>
// Warning (custom)
<Alert className="border-warning/50 bg-warning/10">
<AlertTriangle className="h-4 w-4 text-warning" />
<AlertTitle className="text-warning">Warning</AlertTitle>
<AlertDescription>...</AlertDescription>
</Alert>Dialog / Modal
<Dialog>
<DialogTrigger asChild>
<Button>Open Modal</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit Employee</DialogTitle>
<DialogDescription>
Make changes to the employee profile here.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Form fields */}
</div>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button>Save Changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>Dialog Animation
Use Framer Motion for entrance:
<DialogContent
className="sm:max-w-[425px]"
// Radix handles animation, but for custom:
>Framer Motion variant (if custom):
const modalVariants = {
initial: { opacity: 0, y: 20, scale: 0.95 },
animate: { opacity: 1, y: 0, scale: 1 },
exit: { opacity: 0, y: 20, scale: 0.95 },
};
const transition = { type: "spring", damping: 25, stiffness: 300 };Confirmation Dialog
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">Delete Employee</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
employee record and remove their data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>Loading Button
<Button disabled>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Please wait
</Button>Progress
<Progress value={66} className="h-2" />Progress with Label
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>Uploading...</span>
<span>66%</span>
</div>
<Progress value={66} className="h-2" />
</div>Tooltip
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon">
<HelpCircle className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>This is helpful information</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>Badge with Tooltip
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Badge variant="warning">Pending</Badge>
</TooltipTrigger>
<TooltipContent>
<p>Awaiting manager approval</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>