Toast
Temporary notification that auto-dismisses.
To trigger a toast from any element, add the attribute data-component="toast" and configure it with data attributes:
data-toast-type="info"–info,success,warning, orerrordata-toast-position="top-right"– see Params for all positionsdata-toast-dismissible="true"– whether a close button is showndata-toast-value="Your message"– the toast text
Not dismissible
Toasts can be made non‑dismissible by setting data-toast-dismissible="false". They will disappear automatically after the duration expires and cannot be closed manually.
Warning
Use data-toast-type="warning" to display a warning notification with appropriate styling and icon.
Error
Use data-toast-type="error" for error messages. Error toasts typically have a red border and background.
Usage
Include the JavaScript code in your project:
/**
* Inject necessary CSS animations for toast transitions
*/
const inject_toast_styles = () => {
if (document.getElementById("toast-animation")) return;
const style = document.createElement("style");
style.id = "toast-animation";
style.textContent = `
@keyframes fade-in { 0% { opacity: 0; transform: scale(0.95); } 100% { opacity: 1; transform: scale(1); } }
@keyframes fade-out { 0% { opacity: 1; transform: scale(1); } 100% { opacity: 0; transform: scale(0.95); } }
`;
document.head.appendChild(style);
};
// Position configuration mapping
const POSITION_CLASSES = {
"top-left": "top-0 left-0",
"top-center": "top-0 left-[50%] -translate-x-[50%]",
"top-right": "top-0 right-0",
"bottom-left": "bottom-0 left-0",
"bottom-center": "bottom-0 left-[50%] -translate-x-[50%]",
"bottom-right": "bottom-0 right-0"
};
// Toast type configuration
const TOAST_CONFIGS = {
info: {
classes: "border-black-100 dark:border-black-700 bg-background",
text: "Notification"
},
success: {
classes: "border-green-500 bg-green-50 dark:bg-green-900",
text: "Success"
},
warning: {
classes: "border-yellow-500 bg-yellow-50 dark:bg-yellow-900",
text: "Alert"
},
error: {
classes: "border-red-500 bg-red-50 dark:bg-red-900",
text: "Error"
}
};
/**
* Create toast container with specified position
*/
const create_toast_container = (position) => {
const container = document.createElement("div");
container.className = `fixed z-[9999] p-16px flex flex-col gap-8px pointer-none ${POSITION_CLASSES[position] || ""} toast-container`;
container.dataset.position = position;
return container;
};
/**
* Get existing toast container or create new one
*/
const get_container = (position) => {
const existing = document.querySelector(`.toast-container[data-position="${position}"]`);
if (existing) return existing;
const container = create_toast_container(position);
document.body.appendChild(container);
return container;
};
// Optimized SVG icons (removed duplicate clipPath definitions)
const TOAST_ICONS = {
info: `<svg width="28" height="29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6666 7.5H8.16665C7.54781 7.5 6.95432 7.74583 6.51673 8.18342C6.07915 8.621 5.83331 9.21449 5.83331 9.83333V20.3333C5.83331 20.9522 6.07915 21.5457 6.51673 21.9832C6.95432 22.4208 7.54781 22.6667 8.16665 22.6667H18.6666C19.2855 22.6667 19.879 22.4208 20.3166 21.9832C20.7541 21.5457 21 20.9522 21 20.3333V16.8333" stroke="#ABADB8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.8333 12.166C21.7663 12.166 23.3333 10.599 23.3333 8.66602C23.3333 6.73302 21.7663 5.16602 19.8333 5.16602C17.9003 5.16602 16.3333 6.73302 16.3333 8.66602C16.3333 10.599 17.9003 12.166 19.8333 12.166Z" stroke="#ABADB8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`,
success: `<svg width="28" height="29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.83331 14.4993L11.6666 20.3327L23.3333 8.66602" stroke="#7CCF00" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`,
warning: `<svg width="28" height="29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 25C19.799 25 24.5 20.299 24.5 14.5C24.5 8.70101 19.799 4 14 4C8.20101 4 3.5 8.70101 3.5 14.5C3.5 20.299 8.20101 25 14 25Z" stroke="#F0B100" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 9.83398V14.5007" stroke="#F0B100" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 19.166H14.0117" stroke="#1D1D1F" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`,
error: `<svg width="28" height="29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 7.5L7 21.5" stroke="#EE5449" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 7.5L21 21.5" stroke="#EE5449" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`
};
const ANIMATION_CLASSES = {
enter: "animate-[fade-in_.3s_ease-out]",
exit: "animate-[fade-out_.3s_ease-out]"
};
const DEFAULT_OPTIONS = {
type: "info",
duration: 3000,
position: "bottom-right",
dismissible: true,
max_toasts: 5,
value: "Toast content"
};
// Reusable close button SVG
const CLOSE_ICON = `<svg width="20" height="21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 5.5L5 15.5M5 5.5L15 15.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
class Toast {
/**
* Generate toast DOM element with specified options
*/
static #generate_toast_element(options) {
const config = TOAST_CONFIGS[options.type] || TOAST_CONFIGS.info;
const toast = document.createElement("div");
toast.className = `flex gap-12px py-16px px-20px items-center pointer-events-auto rounded-8px border min-w-[320px] max-w-[550px] w-[80vw] text-foreground ${config.classes} shadow-[0px_6px_24px_-7px_#0000002E] toast ${ANIMATION_CLASSES.enter}`;
toast.setAttribute("role", "alert");
toast.setAttribute("aria-live", "polite");
// Icon
const icon = document.createElement("div");
icon.className = "grid items-center justify-center";
icon.innerHTML = TOAST_ICONS[options.type] || TOAST_ICONS.info;
toast.appendChild(icon);
// Text content
const text_container = document.createElement("div");
text_container.className = "grid ps-4px";
const header = document.createElement("span");
header.className = "text-h10";
header.textContent = config.text;
text_container.appendChild(header);
const message = document.createElement("span");
message.className = "text-p4";
message.textContent = options.message;
text_container.appendChild(message);
toast.appendChild(text_container);
// Close button
if (options.dismissible && options.dismissible !== "false") {
const close_btn = document.createElement("button");
close_btn.className = "text-foreground opacity-80 hover:opacity-100 transition-opacity duration-300 ms-auto js_toast-close";
close_btn.innerHTML = CLOSE_ICON;
close_btn.setAttribute("aria-label", "Close notification");
close_btn.onclick = () => this.#remove_toast(toast);
toast.appendChild(close_btn);
}
return toast;
}
/**
* Remove toast with fade-out animation
*/
static #remove_toast(toast) {
toast.classList.remove(ANIMATION_CLASSES.enter);
toast.classList.add(ANIMATION_CLASSES.exit);
const removeElement = () => {
toast.remove();
// Clean up empty container
const container = toast.parentElement;
if (container && !container.children.length) {
container.remove();
}
};
toast.addEventListener("animationend", removeElement, { once: true });
}
/**
* Display toast with given options
*/
static show(options) {
const merged = { ...DEFAULT_OPTIONS, ...options };
const container = get_container(merged.position);
const toast = this.#generate_toast_element(merged);
// Remove oldest toasts if exceeding limit
const toasts = container.querySelectorAll(".toast");
const overflow = toasts.length + 1 - merged.max_toasts;
if (overflow > 0) {
for (let i = 0; i < overflow; i++) {
this.#remove_toast(toasts[i]);
}
}
container.appendChild(toast);
let timeout_id = null;
if (merged.duration > 0) {
timeout_id = setTimeout(() => {
console.log(merged.duration);
this.#remove_toast(toast);
merged.onClose?.();
}, merged.duration);
}
// Handle close button timeout cleanup
const close_btn = toast.querySelector(".js_toast-close");
if (close_btn && merged.dismissible && merged.dismissible !== "false") {
const original_click = close_btn.onclick;
close_btn.onclick = (e) => {
if (timeout_id) clearTimeout(timeout_id);
merged.onClose?.();
original_click?.call(close_btn, e);
};
}
}
// Convenience methods
static success(message, options) {
this.show({ ...options, message, type: "success" });
}
static error(message, options) {
this.show({ ...options, message, type: "error" });
}
static warning(message, options) {
this.show({ ...options, message, type: "warning" });
}
static info(message, options) {
this.show({ ...options, message, type: "info" });
}
/**
* Remove all currently displayed toasts
*/
static clear() {
document.querySelectorAll(".toast").forEach(toast => {
this.#remove_toast(toast);
});
}
}
// Toast type to method mapping
const TOAST_METHODS = {
"info": Toast.info,
"success": Toast.success,
"warning": Toast.warning,
"error": Toast.error
};
/**
* Get toast options from element dataset
*/
const get_toast_options = (element, globalMaxToasts) => ({
type: element.dataset.toastType ?? DEFAULT_OPTIONS.type,
duration: element.dataset.toastDuration ? +element.dataset.toastDuration : DEFAULT_OPTIONS.duration,
position: element.dataset.toastPosition ?? DEFAULT_OPTIONS.position,
dismissible: element.dataset.toastDismissible ?? DEFAULT_OPTIONS.dismissible,
value: element.dataset.toastValue ?? DEFAULT_OPTIONS.value,
max_toasts: globalMaxToasts
});
/**
* Initialize toast functionality for data-attribute driven toasts
*/
const init_toasts = (options) => {
inject_toast_styles();
const { max_toasts = DEFAULT_OPTIONS.max_toasts } = options || {};
document.querySelectorAll("[data-component='toast']").forEach(elem => {
const toast_options = get_toast_options(elem, max_toasts);
const toast_method = TOAST_METHODS[toast_options.type] || TOAST_METHODS.info;
elem.addEventListener("click", () => {
toast_method.call(Toast, toast_options.value, toast_options);
});
});
};
/**
* Programmatically show a toast with specified options
*/
const show_toast = (options) => {
inject_toast_styles();
const {
max_toasts = DEFAULT_OPTIONS.max_toasts,
type = DEFAULT_OPTIONS.type,
duration = DEFAULT_OPTIONS.duration,
position = DEFAULT_OPTIONS.position,
dismissible = DEFAULT_OPTIONS.dismissible,
value = DEFAULT_OPTIONS.value
} = options || {};
const toast_method = TOAST_METHODS[type] || TOAST_METHODS.info;
toast_method.call(Toast, value, { duration, position, dismissible, max_toasts });
};
window.init_toasts = init_toasts;
window.show_toast = show_toast;
Then initialise the toast system:
init_toasts();
You can pass an optional configuration object to init_toasts() – see init_toasts params below.
After initialisation, any element with data-component="toast" will trigger a toast on click. You can also call toasts programmatically using the show_toast() function.
Auto‑initialisation
Add this at the end of your toast.js file:
document.addEventListener("DOMContentLoaded", () => init_toasts());
Then link the script in your HTML:
<script src="toast.js"></script>
Params
init_toasts params
The init_toasts() function accepts an optional object with the following property:
init_toasts({
max_toasts: 4, // maximum toast counter
});
Custom JS Toast call
You can show a toast programmatically using the global show_toast() function:
show_toast({
max_toasts: 4, // maximum toast counter
type: "info", // 'info' | 'success' | 'warning' | 'error'
duration: 5000, // toast duration
position: "top-center", // 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
dismissible: true, // can user dismiss a toast
value: "show_toast test" // toast text
});
Data Attributes
All options available in show_toast() can also be set via data attributes on the trigger element (prefixed with data-toast-):
data-toast-type– string:'info','success','warning','error'(default:'info')data-toast-duration– number: time in milliseconds before auto‑dismiss (default:3000)data-toast-position– string:'top-left','top-center','top-right','bottom-left','bottom-center','bottom-right'(default:'bottom-right')data-toast-dismissible– boolean:'true'or'false'(default:'true')data-toast-value– string: the toast message (default:'Toast content')
These attributes override any global defaults.