I got tired of rewriting the same toast on every client project, so here’s the version I settled on. It’s only the visual piece. You decide where it sits and when it disappears.
import { CheckCircle2, AlertTriangle, Info, X } from "lucide-react";
const ICONS = { success: CheckCircle2, warning: AlertTriangle, info: Info };
const COLORS = { success: "text-accent dark:text-accent-dark", warning: "text-warn dark:text-warn-dark", info: "text-muted dark:text-muted-dark" };
export default function Toast({ variant = "info", title, onClose }) {
const Icon = ICONS[variant];
return (
<div role={variant === "warning" ? "alert" : "status"} className="flex items-start gap-3 rounded-md border border-line dark:border-line-dark bg-surface dark:bg-surface-dark px-4 py-3 shadow-lg w-full max-w-sm">
<Icon size={18} className={`mt-0.5 shrink-0 ${COLORS[variant]}`} />
<p className="text-sm flex-1">{title}</p>
{onClose && (
<button onClick={onClose} aria-label="Dismiss" className="text-muted dark:text-muted-dark hover:text-ink dark:hover:text-ink-dark">
<X size={14} />
</button>
)}
</div>
);
}
It needs darkMode: "class", lucide-react, and these colors in tailwind.config.js:
ink: { DEFAULT: "#0B0C0E", dark: "#EDEDEF" },
surface: { DEFAULT: "#FFFFFF", dark: "#15161A" },
muted: { DEFAULT: "#63666F", dark: "#8B8E97" },
line: { DEFAULT: "#E3E3E6", dark: "#26272C" },
accent: { DEFAULT: "#00695C", dark: "#00D9C0" },
warn: { DEFAULT: "#8A5A00", dark: "#E5A100" },
The light and dark colors are separate on purpose, so the text stays readable in both. Use it however you like.
It's one of 16 I put together as Forge, with a live demo of all of them: https://forge-ui-demo.netlify.app/
If you want the full set: https://mugandadizo.gumroad.com/l/forge?utm_source=devto&utm_medium=sample_toast&utm_campaign=forge_launch
What would you change?
Top comments (0)