Accessible Toast Notification Component with HTML, CSS, and JavaScript
• Updated 6/26/2026 • HTMLCSSJavaScriptAccessibilityToastUI ComponentARIA
One-file front-end editor
Write plain HTML, CSS, JavaScript, or CDN-powered Vue and React demos.
Use Case
This snippet helps you add accessible toast notifications to a real web project without using a framework or external library. A toast is a small message that appears temporarily to confirm an action, warn the user, or show a short update.
You can use this component for actions like saving a profile, copying text, submitting a form, deleting an item, or showing a short error message after an API request.
- Shows messages without blocking the page.
- Supports success, error, warning, and info styles.
- Can auto-dismiss after a custom duration.
- Includes a keyboard-accessible close button.
- Uses ARIA live regions so screen readers can announce messages.
Concept Overview
A toast notification should be helpful, short, and non-disruptive. It should not replace important form validation messages or critical confirmation dialogs. For accessibility, this snippet uses a live region so assistive technologies can announce new messages when they appear.
aria-live="polite"is used for normal messages such as success, info, and warning notifications.aria-live="assertive"is used only for error messages that need more immediate attention.- A real
<button>is used for the close action so keyboard users can dismiss the toast. textContentis used for the message to avoid unsafe HTML injection.
Complete Snippet Code
Copy this full example into an HTML file and open it in your browser. The component works with plain HTML, CSS, and JavaScript.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accessible Toast Notification</title>
<style>
:root {
--toast-bg: #1f2937;
--toast-text: #ffffff;
--toast-success: #15803d;
--toast-error: #b91c1c;
--toast-warning: #b45309;
--toast-info: #2563eb;
--toast-radius: 0.75rem;
--toast-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);
}
body {
font-family: Arial, sans-serif;
margin: 2rem;
line-height: 1.5;
}
.demo-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.demo-actions button {
border: 0;
border-radius: 0.5rem;
padding: 0.75rem 1rem;
cursor: pointer;
background: #111827;
color: #ffffff;
font: inherit;
}
.demo-actions button:focus-visible,
.toast__close:focus-visible {
outline: 3px solid #93c5fd;
outline-offset: 3px;
}
.toast-container {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 9999;
display: grid;
gap: 0.75rem;
width: min(360px, calc(100vw - 2rem));
pointer-events: none;
}
.toast {
display: grid;
grid-template-columns: 1fr auto;
gap: 0.75rem;
align-items: start;
padding: 1rem;
border-radius: var(--toast-radius);
color: var(--toast-text);
background: var(--toast-bg);
box-shadow: var(--toast-shadow);
pointer-events: auto;
opacity: 0;
transform: translateY(-12px);
animation: toast-in 180ms ease-out forwards;
}
.toast--success {
background: var(--toast-success);
}
.toast--error {
background: var(--toast-error);
}
.toast--warning {
background: var(--toast-warning);
}
.toast--info {
background: var(--toast-info);
}
.toast__message {
margin: 0;
font-size: 0.95rem;
}
.toast__close {
border: 0;
border-radius: 999px;
width: 1.75rem;
height: 1.75rem;
display: inline-grid;
place-items: center;
cursor: pointer;
color: inherit;
background: rgba(255, 255, 255, 0.16);
font-size: 1.1rem;
line-height: 1;
}
.toast__close:hover {
background: rgba(255, 255, 255, 0.26);
}
.toast.is-removing {
animation: toast-out 160ms ease-in forwards;
}
@keyframes toast-in {
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes toast-out {
to {
opacity: 0;
transform: translateY(-12px);
}
}
@media (max-width: 600px) {
.toast-container {
top: auto;
right: 1rem;
bottom: 1rem;
left: 1rem;
width: auto;
}
}
@media (prefers-reduced-motion: reduce) {
.toast,
.toast.is-removing {
animation-duration: 1ms;
}
}
</style>
</head>
<body>
<h1>Accessible Toast Notification Demo</h1>
<div class="demo-actions">
<button type="button" data-toast="success">Show Success</button>
<button type="button" data-toast="error">Show Error</button>
<button type="button" data-toast="warning">Show Warning</button>
<button type="button" data-toast="info">Show Info</button>
</div>
<div id="toast-container" class="toast-container" aria-live="polite" aria-atomic="true"></div>
<script>
const toastContainer = document.querySelector('#toast-container');
const MAX_VISIBLE_TOASTS = 4;
const DEFAULT_DURATION = 4000;
function showToast(message, options = {}) {
if (!toastContainer) return;
const type = options.type || 'info';
const duration = Number.isFinite(options.duration) ? options.duration : DEFAULT_DURATION;
const toast = document.createElement('div');
toast.className = `toast toast--${type}`;
toast.setAttribute('role', type === 'error' ? 'alert' : 'status');
toast.setAttribute('aria-live', type === 'error' ? 'assertive' : 'polite');
const messageElement = document.createElement('p');
messageElement.className = 'toast__message';
messageElement.textContent = message;
const closeButton = document.createElement('button');
closeButton.type = 'button';
closeButton.className = 'toast__close';
closeButton.setAttribute('aria-label', 'Dismiss notification');
closeButton.textContent = '×';
toast.append(messageElement, closeButton);
toastContainer.appendChild(toast);
while (toastContainer.children.length > MAX_VISIBLE_TOASTS) {
toastContainer.firstElementChild.remove();
}
let timeoutId = null;
const removeToast = () => {
clearTimeout(timeoutId);
if (!toast.isConnected) return;
toast.classList.add('is-removing');
toast.addEventListener('animationend', () => toast.remove(), { once: true });
};
closeButton.addEventListener('click', removeToast);
if (duration > 0) {
timeoutId = window.setTimeout(removeToast, duration);
}
return removeToast;
}
document.querySelector('[data-toast="success"]').addEventListener('click', () => {
showToast('Your profile has been saved successfully.', { type: 'success' });
});
document.querySelector('[data-toast="error"]').addEventListener('click', () => {
showToast('Something went wrong. Please try again.', { type: 'error', duration: 6000 });
});
document.querySelector('[data-toast="warning"]').addEventListener('click', () => {
showToast('Your session will expire soon.', { type: 'warning', duration: 7000 });
});
document.querySelector('[data-toast="info"]').addEventListener('click', () => {
showToast('New updates are available.', { type: 'info' });
});
</script>
</body>
</html>
How to Use the Toast
After adding the HTML container, CSS styles, and JavaScript function to your project, you can trigger a toast anywhere in your JavaScript by calling showToast().
showToast('Your changes were saved.', {
type: 'success',
duration: 4000
});
showToast('Unable to connect to the server.', {
type: 'error',
duration: 6000
});
The type option controls the visual style and accessibility behavior. The duration option controls how long the toast stays visible before it closes automatically.
How It Works
The script finds the toast container, creates a new toast element, adds the message safely using textContent, and appends a close button. When the toast is added to the page, CSS animations make it slide into view.
Each toast receives a role based on its type. Normal messages use role="status", while error messages use role="alert". This helps screen readers understand how urgently the message should be announced.
The component also limits the number of visible toasts using MAX_VISIBLE_TOASTS. This prevents the screen from becoming crowded if many notifications are triggered quickly.
Customization Notes
- Change the position: Edit
.toast-container. For example, usebottom: 1rem;instead oftop: 1rem;to place toasts near the bottom. - Change the colors: Update the CSS variables such as
--toast-success,--toast-error, and--toast-info. - Make toasts stay until closed: Pass
duration: 0when callingshowToast(). - Show fewer toasts: Lower the value of
MAX_VISIBLE_TOASTSfrom4to2or3. - Add icons: Add an icon element before the message text, but keep the message readable without relying only on color or icons.
Common Mistakes
- Using
innerHTMLfor user-controlled messages: This can create security issues. UsetextContentfor plain messages. - Making the duration too short: A toast that disappears too quickly can be hard to read. A good default is usually between 4000 and 6000 milliseconds.
- Using toast messages for critical decisions: Important actions, confirmations, and destructive warnings should use clear inline messages or dialogs instead.
- Forgetting keyboard users: Always use a real button for dismissing the toast and include a clear
aria-label. - Relying only on color: Color helps visually, but the message text should still clearly explain what happened.
- Letting unlimited toasts stack: Without a limit, repeated actions can fill the screen with notifications.
Related Practice Ideas
- Add a progress bar that shows how much time is left before the toast closes.
- Create a queue so only one toast appears at a time.
- Add support for bottom-left, bottom-right, top-left, and top-center positions.
- Build a small form demo that shows success and error toasts after validation.
- Convert the snippet into a reusable JavaScript module for multiple pages.

