Project

Toast Notification Library

A small GPL-3 library you can paste beside any HTML: one script, one stylesheet, and a single showToast(options) call. It targets the boring problems - stacking, positions, auto-dismiss, optional progress - and leaves frameworks out of the story so demos, internal tools, and legacy pages can all share the same behaviour.

← Back to projects Contact about front-end work Live demo Source on GitHub

What it is

The library is a ToastManager class plus thin globals: showToast, dismissToast, clearAllToasts, and convenience helpers like showToast.success(text, options). There is no npm install step in the README on purpose - you link toast.css and toast.js, and you are done. That matches how the live demo runs on static hosting.

Each notification is a div.toast-item with inner structure: a .toast-content block (icon span + text paragraph), an optional × close button, and an optional .toast-timer bar when duration is greater than zero and showTimerBar is true. Containers are created per corner (or center): .toast-container.top-right and siblings are mounted on document.body the first time that position is used.

Typing uses six preset kinds - success, error, warning, info, reminder, and custom - each with a default gradient and emoji icon in typeConfig. You can override appearance per toast with customStyles (inline styles plus an optional extra class) or by editing toast.css for site-wide defaults.

  • Zero runtime dependencies beyond the browser DOM APIs.
  • Escape clears every toast; individual close buttons call dismiss with stopPropagation so they do not fire a click-to-navigate toast.
  • Maximum of five toasts per position; the oldest is removed before adding another.
  • duration: 0 keeps a toast until manually closed (still respects close: true/false).
  • role="alert", aria-live="polite", and tabindex="0" so assistive tech can reach the surface.

Calling the API

show merges your object with defaults: type info, position top-right, duration 3000 ms, close true, stopOnFocus true, newWindow true, showTimerBar true, and an empty customStyles object. The only hard requirement is text; without it the library logs an error and returns null.

destination turns the whole toast into a clickable surface: a click handler opens window.open with noopener/noreferrer when newWindow is true, otherwise it assigns window.location.href. Icons come from customStyles.icon if set, otherwise from typeConfig for the chosen type, with a speech-bubble fallback.

showToast({
  text: "Saved successfully",
  type: "success",
  position: "bottom-right",
  duration: 4000,
  close: true,
  stopOnFocus: true,
  showTimerBar: true,
  destination: "https://example.com/log",
  newWindow: false,
  customStyles: {
    borderRadius: "10px",
    customClass: "site-toast"
  }
});

// Shorthand helpers mirror the same options object
showToast.error("Network failed", { duration: 0, close: true });

Convenience methods are sugar: they spread your options and set type for you. Timer behaviour ties together the timeout in startTimer and the CSS width transition on .toast-timer so the bar shrinks linearly over remainingTime; pauseTimer clears the timeout, snapshots bar width, and resumeTimer restarts both bar animation and timeout from the reduced budget.

How the code is structured

On construction, ToastManager initialises two Maps: toasts (numeric id → { element, config, timer, timerBar, startTime, remainingTime, isPaused }) and containers (position key → DOM node). init injects a link rel=stylesheet for toast.css if #toast-styles is missing, and registers a document keydown listener that calls clearAll when Escape is pressed.

createToastElement builds the tree, wires hover/focus pause when stopOnFocus is true and duration is positive, and mounts the timer bar children when requested. getContainer lazily creates absolutely positioned flex stacks per class toast-container ${position}. enforceMaxToasts removes the earliest .toast-item in that stack before you exceed maxToasts.

// Merged config + lifecycle (conceptual excerpt)
const config = { ...this.defaults, ...options };
this.enforceMaxToasts(config.position);
const container = this.getContainer(config.position);
const toastElement = this.createToastElement(toastId, config);
container.appendChild(toastElement);
requestAnimationFrame(() => this.animateIn(toastElement, config.position));
if (config.duration > 0) this.startTimer(toastId);

// Published on window after load
const toastManager = new ToastManager();
window.showToast = (options) => toastManager.show(options);

Animations are class-driven: toast-enter / toast-enter-active (and from-left / from-center where relevant) handle the entrance; toast-exit / toast-exit-active plus directional to-* classes handle the 300 ms exit path before the node is removed. applyCustomStyles maps known keys from customStyles onto inline style properties and can append customClass for site-specific rules.

  • dismiss clears timeouts, plays animateOut, removes the node, and drops empty containers from the containers map.
  • Keyboard focus inside the toast participates in pause/resume alongside mouse hover.
  • The timer inner gradient is a separate absolutely positioned layer for a subtle highlight over the shrinking bar.

CSS and theming

toast.css owns layout, stacking context, z-index, and the keyframe or transition assumptions the JS toggles. Because types are expressed as classes on .toast-item (for example .toast-item.success), you can recolour presets without touching JavaScript - only duplicate the selector specificity you need.

For branded campaigns, prefer customStyles for one-off tweaks and reserve CSS overrides for design-system tokens (spacing, shadow, typography). Keep position classes in sync with the strings accepted by showToast: top-left, top-right, bottom-left, bottom-right, center.

/* Stronger success colour sitewide */
.toast-item.success {
  background: linear-gradient(135deg, #059669, #047857);
}

/* Clickable toasts: pointer cursor comes from .clickable */
.toast-item.clickable {
  cursor: pointer;
}

/* Pause state during hover/focus (class added in JS) */
.toast-timer.paused {
  transition: none !important;
}

Try it and ship it

The interactive demo exercises positions, long durations, stress tests, and custom styling in one page - use it as a regression checklist when you fork the repo or bump styling.

Source and license live on GitHub under GPL-3.0; forks must stay compliant, but that is explicit rather than hidden in a generic MIT file. When you bundle for production, consider stripping verbose console logging if you keep a fork with debug hooks in createToastElement/startTimer.