Feedback feedbacktoastnotificationalertstack

Toast Notification Group

A modern stacked toast notification tray supporting success, error, warning, and info alert toasts with dismiss actions.

Preview

Usage

Copy the full block below to use this component with its imports.

astro
---
import { ToastNotificationGroup } from 'astro-component-kit';
---

<ToastNotificationGroup toasts={[{ id: '1', title: 'Saved', message: 'Project configurations synchronized.', variant: 'success' }]} />
--- import { ToastNotificationGroup } from 'astro-component-kit'; --- <ToastNotificationGroup toasts={[{ id: '1', title: 'Saved', message: 'Project configurations synchronized.', variant: 'success' }]} />

Manual Installation

If you are not using the npm package, create a new file src/components/lib/ToastNotificationGroup.astro and paste the following code:

astro
---
/**
 * ToastNotificationGroup — Stackable notification banner with auto-timed progress bar, icon, and dismiss action.
 * 
 * @param {'success' | 'warning' | 'error' | 'info'} type - Alert status variant. Default is 'success'.
 * @param {string} title - Notification title headline. Default is 'Changes Saved'.
 * @param {string} message - Explanatory message text.
 * @param {string} actionText - Optional action link text (e.g. 'Undo').
 */
interface Props {
  type?: 'success' | 'warning' | 'error' | 'info';
  title?: string;
  message?: string;
  actionText?: string;
}

const {
  type = 'success',
  title = 'Changes Saved',
  message = 'Your project settings were synchronized with production.',
  actionText = 'Undo',
} = Astro.props;

const icons = {
  success: '✓',
  warning: '!',
  error: '✕',
  info: 'ℹ',
};
---

<div class:list={['toast-item', `toast-item--${type}`]} role="alert" data-toast-item>
  <div class="toast-item__body">
    <span class="toast-item__icon" aria-hidden="true">{icons[type]}</span>
    <div class="toast-item__content">
      <h5 class="toast-item__title">{title}</h5>
      <p class="toast-item__msg">{message}</p>
    </div>
    {actionText && (
      <button type="button" class="toast-item__action">
        {actionText}
      </button>
    )}
    <button type="button" class="toast-item__close" aria-label="Dismiss alert" data-toast-close>
      ✕
    </button>
  </div>
  <div class="toast-item__progress" aria-hidden="true"></div>
</div>

<style>
  .toast-item {
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 380px;
    background: rgba(14, 14, 18, 0.95);
    backdrop-filter: blur(20px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: var(--r-md, 14px);
    overflow: hidden;
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.05);
    font-family: var(--font-sans, system-ui, sans-serif);
    transition: transform 0.25s ease, opacity 0.25s ease;
  }

  .toast-item--success { --toast-accent: #10b981; }
  .toast-item--warning { --toast-accent: #f59e0b; }
  .toast-item--error   { --toast-accent: #ef4444; }
  .toast-item--info    { --toast-accent: #38bdf8; }

  .toast-item__body {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 1rem 1.15rem;
  }

  .toast-item__icon {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid var(--toast-accent);
    color: var(--toast-accent);
    display: grid;
    place-items: center;
    font-size: 0.75rem;
    font-weight: 900;
    flex-shrink: 0;
    margin-top: 2px;
  }

  .toast-item__content {
    flex: 1;
  }

  .toast-item__title {
    font-size: 0.88rem;
    font-weight: 700;
    color: var(--c-text-1, #ffffff);
    margin: 0 0 2px 0;
  }

  .toast-item__msg {
    font-size: 0.78rem;
    color: var(--c-text-2, #a3a3a3);
    margin: 0;
    line-height: 1.4;
  }

  .toast-item__action {
    background: transparent;
    border: none;
    color: var(--toast-accent);
    font-size: 0.78rem;
    font-weight: 700;
    cursor: pointer;
    padding: 0.2rem 0.4rem;
    border-radius: 4px;
    transition: background 0.2s;
  }

  .toast-item__action:hover {
    background: rgba(255, 255, 255, 0.08);
  }

  .toast-item__close {
    background: transparent;
    border: none;
    color: var(--c-text-3, #737373);
    font-size: 0.85rem;
    cursor: pointer;
    padding: 0.2rem;
    line-height: 1;
    transition: color 0.2s;
  }

  .toast-item__close:hover {
    color: var(--c-text-1, #ffffff);
  }

  .toast-item__progress {
    height: 3px;
    background: var(--toast-accent);
    width: 100%;
    animation: toast-timer 5s linear forwards;
  }

  @keyframes toast-timer {
    from { width: 100%; }
    to { width: 0%; }
  }
</style>

<script>
  function setupToasts() {
    document.querySelectorAll('[data-toast-item]').forEach((toast) => {
      const closeBtn = toast.querySelector('[data-toast-close]');
      if (closeBtn) {
        closeBtn.addEventListener('click', () => {
          (toast as HTMLElement).style.opacity = '0';
          (toast as HTMLElement).style.transform = 'translateY(10px) scale(0.95)';
          setTimeout(() => toast.remove(), 250);
        });
      }
    });
  }

  setupToasts();
  document.addEventListener('astro:page-load', setupToasts);
</script>
--- /** * ToastNotificationGroup — Stackable notification banner with auto-timed progress bar, icon, and dismiss action. * * @param {'success' | 'warning' | 'error' | 'info'} type - Alert status variant. Default is 'success'. * @param {string} title - Notification title headline. Default is 'Changes Saved'. * @param {string} message - Explanatory message text. * @param {string} actionText - Optional action link text (e.g. 'Undo'). */ interface Props { type?: 'success' | 'warning' | 'error' | 'info'; title?: string; message?: string; actionText?: string; } const { type = 'success', title = 'Changes Saved', message = 'Your project settings were synchronized with production.', actionText = 'Undo', } = Astro.props; const icons = { success: '✓', warning: '!', error: '✕', info: 'ℹ', }; --- <div class:list={['toast-item', `toast-item--${type}`]} role="alert" data-toast-item> <div class="toast-item__body"> <span class="toast-item__icon" aria-hidden="true">{icons[type]}</span> <div class="toast-item__content"> <h5 class="toast-item__title">{title}</h5> <p class="toast-item__msg">{message}</p> </div> {actionText && ( <button type="button" class="toast-item__action"> {actionText} </button> )} <button type="button" class="toast-item__close" aria-label="Dismiss alert" data-toast-close> ✕ </button> </div> <div class="toast-item__progress" aria-hidden="true"></div> </div> <style> .toast-item { position: relative; display: flex; flex-direction: column; width: 100%; max-width: 380px; background: rgba(14, 14, 18, 0.95); backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: var(--r-md, 14px); overflow: hidden; box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.05); font-family: var(--font-sans, system-ui, sans-serif); transition: transform 0.25s ease, opacity 0.25s ease; } .toast-item--success { --toast-accent: #10b981; } .toast-item--warning { --toast-accent: #f59e0b; } .toast-item--error { --toast-accent: #ef4444; } .toast-item--info { --toast-accent: #38bdf8; } .toast-item__body { display: flex; align-items: flex-start; gap: 12px; padding: 1rem 1.15rem; } .toast-item__icon { width: 24px; height: 24px; border-radius: 50%; background: rgba(255, 255, 255, 0.06); border: 1px solid var(--toast-accent); color: var(--toast-accent); display: grid; place-items: center; font-size: 0.75rem; font-weight: 900; flex-shrink: 0; margin-top: 2px; } .toast-item__content { flex: 1; } .toast-item__title { font-size: 0.88rem; font-weight: 700; color: var(--c-text-1, #ffffff); margin: 0 0 2px 0; } .toast-item__msg { font-size: 0.78rem; color: var(--c-text-2, #a3a3a3); margin: 0; line-height: 1.4; } .toast-item__action { background: transparent; border: none; color: var(--toast-accent); font-size: 0.78rem; font-weight: 700; cursor: pointer; padding: 0.2rem 0.4rem; border-radius: 4px; transition: background 0.2s; } .toast-item__action:hover { background: rgba(255, 255, 255, 0.08); } .toast-item__close { background: transparent; border: none; color: var(--c-text-3, #737373); font-size: 0.85rem; cursor: pointer; padding: 0.2rem; line-height: 1; transition: color 0.2s; } .toast-item__close:hover { color: var(--c-text-1, #ffffff); } .toast-item__progress { height: 3px; background: var(--toast-accent); width: 100%; animation: toast-timer 5s linear forwards; } @keyframes toast-timer { from { width: 100%; } to { width: 0%; } } </style> <script> function setupToasts() { document.querySelectorAll('[data-toast-item]').forEach((toast) => { const closeBtn = toast.querySelector('[data-toast-close]'); if (closeBtn) { closeBtn.addEventListener('click', () => { (toast as HTMLElement).style.opacity = '0'; (toast as HTMLElement).style.transform = 'translateY(10px) scale(0.95)'; setTimeout(() => toast.remove(), 250); }); } }); } setupToasts(); document.addEventListener('astro:page-load', setupToasts); </script>

Quick Info

Category
Feedback
Filename
ToastNotificationGroup.astro
Dependencies
None — pure Astro + CSS
Tags
feedbacktoastnotificationalertstack