1 GitHub star = 1 new component.

Star the repo
Feedbackfeedbacktoastnotificationalertstack

Toast Notification Group

Stacked toast notifications manager with enter/exit transitions.

Live Preview

Usage

Copy and paste this code into your React component:

tsx
import { ToastNotificationGroup } from 'react-component-kit-ui/feedback';

<ToastNotificationGroup
  type="success"
  title="Changes saved"
  message="Your project settings were synchronized with production."
  actionText="Undo"
  onAction={undo}
  onDismiss={() => setToast(null)}
/>

Manual Installation

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

ToastNotificationGroup.tsx
'use client';

import { useState } from 'react';
import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
import './ToastNotificationGroup.css';

/**
 * ToastNotificationGroup β€” Stackable notification banner with auto-timed progress bar, icon, and dismiss action.
 *
 * @param type - Optional. 'success' | 'warning' | 'error' | 'info'. Default 'success'.
 * @param title - Optional. Default 'Changes Saved'.
 * @param message - Optional.
 * @param actionText - Optional. Default 'Undo'. Pass '' to hide.
 * @param onAction - Optional. Action button handler.
 * @param onDismiss - Optional. Called after the dismiss animation ends.
 */
export interface ToastNotificationGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
  type?: 'success' | 'warning' | 'error' | 'info';
  title?: ReactNode;
  message?: ReactNode;
  actionText?: string;
  onAction?: () => void;
  onDismiss?: () => void;
}

const icons = { success: 'βœ“', warning: '!', error: 'βœ•', info: 'β„Ή' };

export function ToastNotificationGroup({
  type = 'success',
  title = 'Changes Saved',
  message = 'Your project settings were synchronized with production.',
  actionText = 'Undo',
  onAction,
  onDismiss,
  className,
  style,
  ...rest
}: ToastNotificationGroupProps) {
  const [state, setState] = useState<'shown' | 'leaving' | 'gone'>('shown');
  if (state === 'gone') return null;

  const dismiss = () => {
    setState('leaving');
    window.setTimeout(() => {
      setState('gone');
      onDismiss?.();
    }, 250);
  };

  const leaving: CSSProperties = state === 'leaving' ? { opacity: 0, transform: 'translateY(10px) scale(0.95)' } : {};

  return (
    <div role="alert" {...rest} className={['toast-item', `toast-item--${type}`, className].filter(Boolean).join(' ')} style={{ ...style, ...leaving }}>
      <div className="toast-item__body">
        <span className="toast-item__icon" aria-hidden="true">{icons[type]}</span>
        <div className="toast-item__content">
          <h5 className="toast-item__title">{title}</h5>
          <p className="toast-item__msg">{message}</p>
        </div>
        {actionText && (
          <button type="button" className="toast-item__action" onClick={onAction}>
            {actionText}
          </button>
        )}
        <button type="button" className="toast-item__close" aria-label="Dismiss alert" onClick={dismiss}>
          βœ•
        </button>
      </div>
      <div className="toast-item__progress" aria-hidden="true" />
    </div>
  );
}

export default ToastNotificationGroup;
ToastNotificationGroup.css
/* ToastNotificationGroup β€” ported from astro-component-kit (feedback/ToastNotificationGroup.astro) */

.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-notification-group-toast-timer 5s linear forwards;
}

@keyframes toast-notification-group-toast-timer {
  from { width: 100%; }
  to { width: 0%; }
}

Quick Info

Category
Feedback
Filename
ToastNotificationGroup.tsx
Dependencies
None β€” pure React + CSS
Tags
feedbacktoastnotificationalertstack