UI Components Easy
Toast Notification Stack
A lightweight toast notification system with success, error, warning, and info variants. Toasts stack, auto-dismiss, and support manual close.
Open in Lab
MCP
vanilla-js css animation
Targets: JS HTML React Native
Expo Snack
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0f172a;
color: #f1f5f9;
min-height: 100vh;
display: grid;
place-items: center;
}
/* โโ Demo controls โโ */
.demo-center {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
}
.demo-title {
font-size: 1.25rem;
font-weight: 600;
color: #94a3b8;
}
.demo-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: center;
}
.btn {
padding: 0.6rem 1.25rem;
border-radius: 0.625rem;
border: 1px solid transparent;
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
transition: opacity 0.15s ease, transform 0.1s ease;
}
.btn:hover {
opacity: 0.85;
transform: translateY(-1px);
}
.btn:active {
transform: translateY(0);
}
.btn--success {
background: rgba(34, 197, 94, 0.15);
border-color: rgba(34, 197, 94, 0.3);
color: #4ade80;
}
.btn--error {
background: rgba(239, 68, 68, 0.15);
border-color: rgba(239, 68, 68, 0.3);
color: #f87171;
}
.btn--warning {
background: rgba(234, 179, 8, 0.15);
border-color: rgba(234, 179, 8, 0.3);
color: #facc15;
}
.btn--info {
background: rgba(56, 189, 248, 0.15);
border-color: rgba(56, 189, 248, 0.3);
color: #38bdf8;
}
/* โโ Toast container โโ */
.toast-container {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
display: flex;
flex-direction: column-reverse;
gap: 0.625rem;
z-index: 9999;
pointer-events: none;
}
/* โโ Toast โโ */
.toast {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.875rem 1rem;
border-radius: 0.75rem;
min-width: 280px;
max-width: 360px;
background: #1e293b;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
pointer-events: all;
animation: toast-in 0.28s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.toast.out {
animation: toast-out 0.22s ease forwards;
}
@keyframes toast-in {
from {
opacity: 0;
transform: translateX(110%) scale(0.9);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
@keyframes toast-out {
from {
opacity: 1;
transform: translateX(0) scale(1);
}
to {
opacity: 0;
transform: translateX(110%) scale(0.9);
}
}
/* โโ Variants โโ */
.toast--success {
border-left: 3px solid #4ade80;
}
.toast--error {
border-left: 3px solid #f87171;
}
.toast--warning {
border-left: 3px solid #facc15;
}
.toast--info {
border-left: 3px solid #38bdf8;
}
.toast__icon {
font-size: 1.125rem;
flex-shrink: 0;
margin-top: 0.05rem;
}
.toast__body {
flex: 1;
}
.toast__title {
font-size: 0.8rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 0.15rem;
}
.toast--success .toast__title {
color: #4ade80;
}
.toast--error .toast__title {
color: #f87171;
}
.toast--warning .toast__title {
color: #facc15;
}
.toast--info .toast__title {
color: #38bdf8;
}
.toast__msg {
font-size: 0.875rem;
color: #94a3b8;
line-height: 1.5;
}
.toast__close {
background: transparent;
border: none;
color: #475569;
font-size: 1.1rem;
line-height: 1;
cursor: pointer;
padding: 0.15rem;
border-radius: 0.25rem;
flex-shrink: 0;
transition: color 0.15s ease;
}
.toast__close:hover {
color: #94a3b8;
}(function () {
"use strict";
const container = document.getElementById("toast-container");
const ICONS = {
success: "โ",
error: "โ",
warning: "โ ",
info: "โน",
};
const TITLES = {
success: "Success",
error: "Error",
warning: "Warning",
info: "Info",
};
/**
* Show a toast notification.
* @param {string} message
* @param {"success"|"error"|"warning"|"info"} type
* @param {number} duration Auto-dismiss delay in ms (default 4000)
*/
function showToast(message, type = "info", duration = 4000) {
const toast = document.createElement("div");
toast.className = `toast toast--${type}`;
toast.setAttribute("role", "status");
toast.setAttribute("aria-live", "polite");
toast.innerHTML = `
<span class="toast__icon" aria-hidden="true">${ICONS[type]}</span>
<div class="toast__body">
<span class="toast__title">${TITLES[type]}</span>
<span class="toast__msg">${message}</span>
</div>
<button class="toast__close" aria-label="Dismiss notification">โ</button>
`;
container.appendChild(toast);
// Auto-dismiss
const timer = setTimeout(() => dismiss(toast), duration);
// Manual dismiss
toast.querySelector(".toast__close").addEventListener("click", () => {
clearTimeout(timer);
dismiss(toast);
});
}
function dismiss(toast) {
toast.classList.add("out");
toast.addEventListener("animationend", () => toast.remove(), { once: true });
}
// Expose globally so onclick attributes work
window.showToast = showToast;
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Toast Notification Stack</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo-center">
<h2 class="demo-title">Toast Notifications</h2>
<div class="demo-buttons">
<button class="btn btn--success" onclick="showToast('Changes saved successfully!', 'success')">
Success
</button>
<button class="btn btn--error" onclick="showToast('Something went wrong. Please try again.', 'error')">
Error
</button>
<button class="btn btn--warning" onclick="showToast('Your session expires in 5 minutes.', 'warning')">
Warning
</button>
<button class="btn btn--info" onclick="showToast('New version available. Refresh to update.', 'info')">
Info
</button>
</div>
</div>
<!-- Toast container โ fixed position, bottom-right -->
<div class="toast-container" id="toast-container" aria-live="polite" aria-atomic="false"></div>
<script src="script.js"></script>
</body>
</html>import React, { createContext, useContext, useCallback, useRef, useState } from "react";
import { View, Text, TouchableOpacity, Animated, StyleSheet } from "react-native";
/* โโ Types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
type ToastVariant = "success" | "error" | "warning" | "info";
interface Toast {
id: number;
message: string;
variant: ToastVariant;
}
interface ToastContextValue {
show: (message: string, variant: ToastVariant) => void;
}
/* โโ Variant styles โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
const VARIANT: Record<ToastVariant, { bg: string; icon: string }> = {
success: { bg: "#16a34a", icon: "\u2713" },
error: { bg: "#dc2626", icon: "\u2717" },
warning: { bg: "#d97706", icon: "\u26A0" },
info: { bg: "#2563eb", icon: "\u2139" },
};
const AUTO_DISMISS = 3500;
/* โโ Single toast โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
function ToastItem({
toast,
onDismiss,
}: {
toast: Toast;
onDismiss: (id: number) => void;
}) {
const translateY = useRef(new Animated.Value(-80)).current;
const opacity = useRef(new Animated.Value(0)).current;
React.useEffect(() => {
Animated.parallel([
Animated.spring(translateY, {
toValue: 0,
useNativeDriver: true,
friction: 8,
}),
Animated.timing(opacity, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
]).start();
const timer = setTimeout(() => dismiss(), AUTO_DISMISS);
return () => clearTimeout(timer);
}, []);
const dismiss = useCallback(() => {
Animated.parallel([
Animated.timing(translateY, {
toValue: -80,
duration: 250,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 0,
duration: 250,
useNativeDriver: true,
}),
]).start(() => onDismiss(toast.id));
}, [toast.id, onDismiss, translateY, opacity]);
const v = VARIANT[toast.variant];
return (
<Animated.View
style={[styles.toast, { backgroundColor: v.bg, transform: [{ translateY }], opacity }]}
>
<Text style={styles.icon}>{v.icon}</Text>
<Text style={styles.message}>{toast.message}</Text>
<TouchableOpacity onPress={dismiss} hitSlop={8}>
<Text style={styles.close}>{"\u2717"}</Text>
</TouchableOpacity>
</Animated.View>
);
}
/* โโ Provider โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
const ToastContext = createContext<ToastContextValue>({
show: () => {},
});
export function useToast() {
return useContext(ToastContext);
}
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([]);
const nextId = useRef(0);
const show = useCallback((message: string, variant: ToastVariant) => {
const id = nextId.current++;
setToasts((prev) => [...prev, { id, message, variant }]);
}, []);
const remove = useCallback((id: number) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, []);
return (
<ToastContext.Provider value={{ show }}>
{children}
<View style={styles.container} pointerEvents="box-none">
{toasts.map((t) => (
<ToastItem key={t.id} toast={t} onDismiss={remove} />
))}
</View>
</ToastContext.Provider>
);
}
/* โโ Styles โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
const styles = StyleSheet.create({
container: {
position: "absolute",
top: 60,
left: 16,
right: 16,
alignItems: "center",
zIndex: 9999,
},
toast: {
flexDirection: "row",
alignItems: "center",
width: "100%",
paddingVertical: 14,
paddingHorizontal: 16,
borderRadius: 12,
marginBottom: 8,
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 6,
elevation: 6,
},
icon: {
color: "#fff",
fontSize: 16,
marginRight: 10,
fontWeight: "700",
},
message: {
color: "#fff",
fontSize: 14,
flex: 1,
},
close: {
color: "rgba(255,255,255,0.7)",
fontSize: 16,
marginLeft: 8,
},
});
/* โโ Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
function DemoContent() {
const { show } = useToast();
const buttons: { label: string; variant: ToastVariant; color: string }[] = [
{ label: "Success", variant: "success", color: "#16a34a" },
{ label: "Error", variant: "error", color: "#dc2626" },
{ label: "Warning", variant: "warning", color: "#d97706" },
{ label: "Info", variant: "info", color: "#2563eb" },
];
return (
<View style={demo.container}>
<Text style={demo.heading}>Toast System</Text>
{buttons.map((b) => (
<TouchableOpacity
key={b.variant}
style={[demo.btn, { backgroundColor: b.color }]}
onPress={() => show(`This is a ${b.variant} toast!`, b.variant)}
>
<Text style={demo.btnText}>Show {b.label}</Text>
</TouchableOpacity>
))}
</View>
);
}
export default function App() {
return (
<ToastProvider>
<DemoContent />
</ToastProvider>
);
}
const demo = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0f172a",
justifyContent: "center",
alignItems: "center",
padding: 24,
},
heading: {
color: "#fff",
fontSize: 22,
fontWeight: "700",
marginBottom: 32,
},
btn: {
width: 200,
paddingVertical: 14,
borderRadius: 10,
alignItems: "center",
marginBottom: 12,
},
btnText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
});Toast Notification Stack
A zero-dependency toast notification system with four semantic variants: success, error, warning, and info. Toasts slide in from the bottom-right, stack vertically, and auto-dismiss after 4 seconds.
How it works
showToast(message, type)creates a<div>with the right variant class and appends it to a fixed container- A CSS
slide-inkeyframe animates entry; aslide-outclass triggers exit before removal - Auto-dismiss uses
setTimeoutโ clicking the ร button cancels the timer and dismisses immediately - Toasts stack naturally via flexbox
column-reverse
Variants
successโ green accent, checkmark iconerrorโ red accent, X iconwarningโ amber accent, triangle iconinfoโ blue accent, info icon
When to use it
- Form submission feedback
- Async operation results (save, delete, upload)
- System alerts and status updates