Toast Notification
Fixed-position notification with auto-dismiss and multiple variants.
Code
components/ToastNotification.astro
---interface Props { position?: "top-right" | "top-left" | "bottom-right" | "bottom-left" | "top-center" | "bottom-center"; maxToasts?: number; class?: string;} const { position = "top-right", maxToasts = 5, class: className = "",} = Astro.props; const positionClasses = { "top-right": "top-4 right-4", "top-left": "top-4 left-4", "bottom-right": "bottom-4 right-4", "bottom-left": "bottom-4 left-4", "top-center": "top-4 left-1/2 -translate-x-1/2", "bottom-center": "bottom-4 left-1/2 -translate-x-1/2",};--- <div id="toast-container" class={`fixed z-50 flex flex-col gap-2 pointer-events-none ${positionClasses[position]} ${className}`} role="status" aria-live="polite"></div> <script define:vars={{ maxToasts }}> var container = document.getElementById("toast-container"); var max = maxToasts; var variantClasses = { solid: "bg-black-onyx text-white-ghost", "left-bordered": "bg-white-ghost border-l-4 border-black-onyx text-black-onyx shadow-lg shadow-black-onyx/10", }; window.showToast = function (opts) { var message = opts.message; var variant = opts.variant || "solid"; var duration = opts.duration !== undefined ? opts.duration : 5000; var id = "toast-" + Date.now() + "-" + Math.random().toString(36).slice(2, 6); while (container.children.length >= max) { var first = container.firstChild; if (first) closeToast(first, true); } var toast = document.createElement("div"); toast.id = id; toast.className = "flex items-start gap-3 px-4 py-3 rounded text-sm pointer-events-auto transition-all duration-300 translate-y-2 opacity-0 " + variantClasses[variant]; toast.setAttribute("role", "alert"); toast.innerHTML = '<div class="flex-1">' + message + '</div>' + '<button class="toast-close shrink-0 text-current/60 hover:text-current transition-colors cursor-pointer" aria-label="Close">' + '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256">' + '<path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>' + '</svg>' + '</button>'; container.appendChild(toast); requestAnimationFrame(function () { toast.classList.remove("translate-y-2", "opacity-0"); }); toast.querySelector(".toast-close").addEventListener("click", function () { closeToast(toast); }); if (duration > 0) { setTimeout(function () { closeToast(toast); }, duration); } }; function closeToast(toast, immediate) { toast.classList.add("translate-y-2", "opacity-0"); if (immediate) { toast.remove(); } else { setTimeout(function () { toast.remove(); }, 300); } }</script>
Preview
Solid Toast
Left Bordered Toast
Long Duration Toast
Usage
Place the ToastNotification component once in your layout, then call
showToast() from anywhere.
<ToastNotification position="top-right" />
Solid Variant
<button onclick="showToast({ message: 'Component code copied to clipboard!', variant: 'solid', duration: 4000 })"> Show Solid Toast</button>
Bordered Variant
<button onclick="showToast({ message: 'Your settings have been saved successfully.', variant: 'left-bordered', duration: 4000 })"> Show Left Bordered Toast</button>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| position | "top-right" | "top-left" | "bottom-right" | "bottom-left" | "top-center" | "bottom-center" | "top-right" | Toast container position. |
| maxToasts | number | 5 | Maximum visible toasts at once. |
| class | string | — | Additional CSS classes. |
showToast Options
| Option | Type | Default | Description |
|---|---|---|---|
| message | string | — | Toast message text. |
| variant | "solid" | "left-bordered" | "solid" | Toast style variant. |
| duration | number | 5000 | Auto-dismiss in ms (0 = persistent). |