Toast Queue — Free HTML CSS JS Snippet

Toast Queue · Modals · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Stacks up to MAX=5 toasts: oldest dismissed when new toast would exceed limit
prepend + flex-direction: column-reverse: newest at DOM top, visually at bottom
CSS progress bar: animation:shrink linear, duration from DURATION constant
Hover pause: mouseenter clears timer + pauses progress, mouseleave resumes
Exit animation: slide right + scale(0.96) + max-height collapse (no gap left)
animationend removes element — no invisible click-blocking remnants
data-timer attribute stores timeout ID per toast for individual control
5 types: success/error/info/warning/neutral with colour-matched border+icon+bar

About this UI Snippet

Toast Queue — Multi-Toast Stack, Progress Bar, Hover Pause, Auto-Dismiss & Max Limit

Screenshot of the Toast Queue snippet rendered live

A toast notification queue manages multiple simultaneous toasts — stacking them, auto-dismissing each after a timeout, pausing on hover, and removing the oldest when a maximum count is reached. This is the production-grade version of a single toast notification. It handles the real-world case where multiple operations complete or fail simultaneously. This snippet provides: five semantic toast types (success, error, info, warning, neutral), a CSS progress bar that drains over the dismiss duration, hover-to-pause, animated entrance and exit, a dismiss-all button, and a configurable maximum stack size.

The toast queue architecture

Toasts are prepended to a column-reverse flex container — prepend adds at the visual bottom of the stack (newest appears below older toasts). The flex-direction: column-reverse means the newest toast is at the DOM top but visually at the bottom-right, appearing to "stack up" from the corner. Each toast gets an individual setTimeout for auto-dismiss stored in a data-timer attribute.

The CSS progress bar

Each toast contains a .toast-progress div with animation: shrink linear forwards. The animation runs for DURATION milliseconds (4000ms by default), shrinking width from 100% to 0%. The animation-duration is set as an inline style from the JavaScript constant. When the user hovers, the animation is paused via animationPlayState and a new shorter timer (1500ms) starts on mouseleave.

Hover-to-pause

On mouseenter, clearTimeout removes the pending auto-dismiss. The progress bar animation is paused. On mouseleave, the animation resumes and a new 1500ms timer starts. This gives users time to read toasts without the timer running while they are focused on the notification.

The maximum stack limit

Before adding a new toast, the queue checks the count of non-leaving toasts. If equal to MAX (5), the oldest toast (last in the column-reverse list) is dismissed first. This prevents the toast stack from growing unboundedly on rapid triggers.

The exit animation

The .leaving class triggers an exit animation: toastOut slides the toast to the right with scale(0.96) and opacity 0, while also animating max-height and padding to 0. This collapse animation removes the toast from the visual stack without leaving a gap.

Integrating with a global event bus

For framework-free apps, dispatch and listen to custom events: window.dispatchEvent(new CustomEvent("toast", { detail: { type:"success", title:"Saved!" } })). Add a listener: window.addEventListener("toast", e => toast(e.detail.type, e.detail.title, e.detail.msg)). Any module or component can now fire toasts without direct function calls. This decoupled pattern prevents circular dependencies in larger applications.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI coding assistant like Claude to trace through what happens to a toast's timer and progress bar the instant you hover it — specifically why mouseenter clears the original setTimeout and pauses the CSS animation, while mouseleave doesn't just resume the original countdown but starts a fresh, shorter one. It's worth a correctness check too: since dismiss() reads data-timer off the element and clearTimeout can silently no-op on an already-fired timer, ask whether there's a race condition when a toast is dismissed manually right as its auto-dismiss timer fires. For extending it, ask for an action button inside a toast that both runs a callback and dismisses it, a deduplication check that bumps an existing toast instead of stacking a duplicate, or grouping toasts by type with a collapsed "3 more" summary once the max is exceeded. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a stacked toast notification queue in plain HTML, CSS, and JavaScript with a maximum visible count, a per-toast countdown progress bar, and hover-to-pause — no library.

Requirements:
- A toast function that creates a new toast element, prepends it into a fixed-position container using flex-direction: column-reverse (so newest toasts visually stack at the bottom while being first in DOM order), and stores its own auto-dismiss setTimeout id on the element itself (e.g. as a data attribute) so each toast's timer can be individually cancelled.
- Before adding a new toast, check the count of currently visible (non-dismissing) toasts against a maximum constant; if the stack is already at that maximum, dismiss the oldest visible toast first so the total never exceeds the configured limit.
- Each toast must include a thin progress bar whose width shrinks from 100% to 0% via a CSS animation matched in duration to the toast's auto-dismiss timer, giving a visual countdown of exactly how much time is left.
- On mouseenter, clear the toast's pending dismiss timer and pause the progress bar's CSS animation (via animationPlayState); on mouseleave, resume the animation and start a new, shorter timer so a toast the user was reading still dismisses promptly rather than lingering indefinitely.
- A single dismiss function that guards against double-dismissal, cancels any pending timer, adds an exit-animation class, and removes the element from the DOM only once that exit animation completes via an animationend listener.
- A "dismiss all" control that iterates every current toast and calls the same dismiss function on each, rather than duplicating the removal logic.

Step by step

How to Use

  1. 1
    Click the trigger buttons to show different toast typesEach button shows a toast with a matching colour, icon, and progress bar. Hover a toast to pause its timer. Add 6 toasts to see the oldest one auto-dismiss when the stack limit is reached.
  2. 2
    Call toast() from anywhere in your codetoast("success", "Saved!", "Optional description") — first arg is the type, second is the title, third is an optional message. Call from API callbacks, form handlers, or any async operation.
  3. 3
    Change the auto-dismiss durationUpdate const DURATION = 4000 at the top of the JS. The progress bar animation-duration updates automatically since it reads from this constant. Use 3000 for quick feedback toasts, 6000 for important warnings.
  4. 4
    Change the maximum simultaneous toastsUpdate const MAX = 5 to control the stack limit. The oldest toast dismisses automatically when a new toast would exceed this limit.
  5. 5
    Change the toast positionUpdate .toast-container CSS: bottom/right for bottom-right (default), top/right for top-right, bottom/left for bottom-left. For centred top toasts, use top:24px; left:50%; transform:translateX(-50%); and change flex-direction to column.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component with a toast context/hook, or "Tailwind" for a React + Tailwind CSS version.

Real-world uses

Common Use Cases

API call feedback and async operation results
Show toast notifications for every async operation: save success, upload error, sync complete, validation failure. The queue handles multiple simultaneous results — if three API calls complete at once, three toasts appear and auto-dismiss independently.
Form submission and data mutation feedback
Replace alert() and inline error messages with toasts. Success toasts for saves, error toasts for failures, info toasts for background operations. The hover-pause gives users time to read error messages and copy IDs before dismissal.
System status and background job notifications
Background jobs (image processing, report generation, data export) can notify completion via toasts. The progress bar signals how long the notification will remain visible, giving users time to act on the information.
Real-time collaboration event notifications
Show toasts when teammates make changes: "Alex joined the document", "Sara made 3 edits", "Raj left the session" — alongside an activity feed for the full history. Use the neutral type for non-urgent collaboration events. The MAX limit prevents overload during active collaboration sessions.
Study toast queue architecture and CSS animation control
The queue demonstrates how to manage a collection of independently timed UI elements using setTimeout IDs stored in data attributes. The animationPlayState pause pattern is applicable to any CSS animation that needs user-controlled pausing.
E-commerce and shopping action confirmations
Cart additions, wishlist saves, coupon applications, and checkout steps all benefit from toast feedback. For undoable actions, use the snackbar with undo instead. The success variant matches the expected positive confirmation; the error variant catches failed actions (out of stock, invalid coupon) immediately.

Got questions?

Frequently Asked Questions

On mouseenter: clearTimeout(+el.dataset.timer) cancels the pending dismiss. el.querySelector(".toast-progress").style.animationPlayState = "paused" freezes the visual progress bar. On mouseleave: the progress animation resumes with animationPlayState = "running". A new setTimeout of 1500ms starts — shorter than the original duration so toasts that were hovered still dismiss reasonably quickly after the user stops hovering. The data-timer attribute holds the current timer ID for each toast independently.

Create a ToastContext: const ToastContext = React.createContext(null). In a ToastProvider, manage toasts as useState([]) and expose a toast() function via the context. Components call const { toast } = useContext(ToastContext). Render the <ToastContainer> inside the provider. This gives any component in the tree access to add toasts without prop drilling. For TypeScript, define a ToastType and use useReducer for more complex state management.

Add a button inside the toast HTML: after the toast-msg div, add <button class="toast-action" onclick="handleAction()">View details</button>. Style .toast-action with font-size:12px; font-weight:700; color:inherit; background:transparent; border:1px solid currentColor; border-radius:6px; padding:3px 10px; cursor:pointer; margin-top:6px. The action button clicking should also dismiss the toast: onclick="handleAction(); dismiss(this.closest('.toast'))".

Add a message ID to each toast: el.dataset.toastId = title + "-" + type. Before adding, check for duplicates: if (container.querySelector("[data-toast-id='" + toastId + "']")) { bump animation on existing toast instead of creating new. }. This prevents "Changes saved" from appearing 5 times if the user clicks save rapidly. The bump animation (brief scale up and down) communicates that a duplicate was received without adding visual clutter.