A lightweight, fully self-contained toast notification system with four types; success, error, warning, and info.
Toasts slide in from the bottom-right, stack cleanly, and auto-dismiss after 3 seconds with a progress bar.
How to Use?
Drop the HTML container and CSS into your page, include the JS, then call
showToast('Your message', 'success') anywhere in your code.
Valid types are success,
error,
warning, and
info.
Credits
Built with pure JS DOM manipulation and CSS animations, zero dependencies.
<!-- Toast Container: place once near end of <body> -->
<div id="toast-container"></div>
<!-- Example trigger buttons -->
<div class="demo-buttons">
<button onclick="showToast('Changes saved successfully!', 'success')">Success</button>
<button onclick="showToast('Something went wrong.', 'error')">Error</button>
<button onclick="showToast('Please review your input.', 'warning')">Warning</button>
<button onclick="showToast('New update available.', 'info')">Info</button>
</div>
#toast-container {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.65rem;
z-index: 9999;
pointer-events: none;
}
.toast {
display: flex;
align-items: flex-start;
gap: 0.75rem;
min-width: 280px;
max-width: 360px;
padding: 0.85rem 1rem;
border-radius: 0.6rem;
background: #1a1a1a;
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 8px 24px rgba(0,0,0,0.5);
color: #e4e4e7;
font-family: 'Inter', sans-serif;
font-size: 0.875rem;
pointer-events: all;
position: relative;
overflow: hidden;
animation: toast-in 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.toast.removing {
animation: toast-out 0.3s ease forwards;
}
@keyframes toast-in {
from { opacity: 0; transform: translateX(110%); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes toast-out {
from { opacity: 1; transform: translateX(0); max-height: 80px; }
to { opacity: 0; transform: translateX(110%); max-height: 0; }
}
.toast::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 4px;
border-radius: 0.6rem 0 0 0.6rem;
}
.toast-progress {
position: absolute;
bottom: 0; left: 0;
height: 2px;
width: 100%;
transform-origin: left;
animation: toast-progress 3s linear forwards;
}
@keyframes toast-progress {
from { transform: scaleX(1); }
to { transform: scaleX(0); }
}
.toast-icon { font-size: 1.1rem; flex-shrink: 0; margin-top: 1px; }
.toast-body { flex: 1; }
.toast-title { font-weight: 600; margin-bottom: 0.1rem; }
.toast-msg { color: rgba(255,255,255,0.55); font-size: 0.8rem; }
.toast-close {
background: none;
border: none;
color: rgba(255,255,255,0.35);
cursor: pointer;
font-size: 0.85rem;
padding: 0;
line-height: 1;
transition: color 0.2s;
flex-shrink: 0;
}
.toast-close:hover { color: rgba(255,255,255,0.8); }
.toast.success::before { background: #22c55e; }
.toast.success .toast-progress { background: #22c55e; }
.toast.success .toast-icon { color: #22c55e; }
.toast.error::before { background: #ef4444; }
.toast.error .toast-progress { background: #ef4444; }
.toast.error .toast-icon { color: #ef4444; }
.toast.warning::before { background: #f59e0b; }
.toast.warning .toast-progress { background: #f59e0b; }
.toast.warning .toast-icon { color: #f59e0b; }
.toast.info::before { background: #8b5cf6; }
.toast.info .toast-progress { background: #8b5cf6; }
.toast.info .toast-icon { color: #8b5cf6; }
const TOAST_ICONS = {
success: '✓',
error: '✗',
warning: '⚠',
info: 'ℹ'
};
const TOAST_TITLES = {
success: 'Success',
error: 'Error',
warning: 'Warning',
info: 'Info'
};
function showToast(message, type = 'info', duration = 3000) {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = 'toast ' + type;
toast.innerHTML =
'<span class="toast-icon">' + TOAST_ICONS[type] + '</span>' +
'<div class="toast-body">' +
'<div class="toast-title">' + TOAST_TITLES[type] + '</div>' +
'<div class="toast-msg">' + message + '</div>' +
'</div>' +
'<button class="toast-close" onclick="removeToast(this.parentElement)">✕</button>' +
'<div class="toast-progress"></div>';
container.appendChild(toast);
const timer = setTimeout(() => removeToast(toast), duration);
toast.addEventListener('mouseenter', () => {
clearTimeout(timer);
toast.querySelector('.toast-progress').style.animationPlayState = 'paused';
});
toast.addEventListener('mouseleave', () => {
toast.querySelector('.toast-progress').style.animationPlayState = 'running';
setTimeout(() => removeToast(toast), 1000);
});
}
function removeToast(toast) {
if (!toast || toast.classList.contains('removing')) return;
toast.classList.add('removing');
toast.addEventListener('animationend', () => toast.remove());
}