Skip to main content
Features

Notification System

Global toast notification system with typed messages, progress tracking, auto-dismiss, and update-by-ID patterns

February 23, 2026

Notification System

NotificationSystem.astro is a global singleton that provides toast notifications across all pages. It renders a fixed-position stack in the top-right corner and exposes its API through window.notify.

Component Setup

PropertyValue
Filesrc/components/admin/NotificationSystem.astro
Rendered inCosmicLayout.astro (all pages using Cosmic layout)
Container#notificationStack, fixed position, z-index 10000
Max visible5 simultaneous — oldest auto-dismissed when exceeded
Script type<script is:inline> to avoid SSR hydration issues

Notification Types

TypeIconColorAuto-Dismiss
successfa-circle-checkGreen (#22c55e)4 seconds
errorfa-circle-exclamationRed (#ef4444)6 seconds
warningfa-triangle-exclamationAmber (#f59e0b)5 seconds
infofa-circle-infoCyan (#06b6d4)4 seconds
progressfa-spinner fa-spinCyan (#06b6d4)None (manual)

Each type gets a matching border color, icon, and glow shadow. The glassmorphic background uses rgba(15, 23, 42, 0.85) with 16px backdrop blur.

API Reference

Shorthand methods

notify.success('Saved!');           // 4s auto-dismiss
notify.error('Upload failed');      // 6s auto-dismiss
notify.warning('Unsaved changes');  // 5s auto-dismiss
notify.info('Loading...');          // 4s auto-dismiss

Progress notifications

notify.progress('Analyzing posts...', 'task-id');

Shows a spinning icon and animated progress bar. Does not auto-dismiss — use notify.update() or notify.dismiss() to clear it.

Update by ID

notify.update('task-id', 'Analysis complete!', 'success');

Finds the existing notification with that ID, swaps the message and type, removes the progress bar, and starts the auto-dismiss timer.

Dismiss by ID

notify.dismiss('task-id');

Triggers the exit animation (250ms) then removes the element.

Generic creator

notify({
  message: 'Custom notification',
  type: 'info',        // default: 'info'
  duration: 3000,      // default: 4000
  progress: false,     // default: false
  id: 'my-id'          // optional, for update/dismiss
});

Legacy alias

showToast('Marked as Reviewed');  // Same as notify.success()

Common Patterns

API call with error handling

try {
  const res = await fetch('/api/admin/publish', { method: 'POST', body: JSON.stringify({ id }) });
  const data = await res.json();
  if (data.success) notify.success('Published!');
  else notify.error('Publish failed: ' + data.error);
} catch (e) {
  notify.error('Error: ' + e.message);
}

Long-running task with progress → completion

notify.progress('Publishing...', 'quick-pub');
const res = await fetch('/api/admin/update-frontmatter', { method: 'POST', ... });
const data = await res.json();
if (data.success) {
  notify.update('quick-pub', 'Published!', 'success');
} else {
  notify.update('quick-pub', 'Failed: ' + data.error, 'error');
}

Defensive window check

In shared code that might run before the component loads:

if ((window as any).notify) {
  (window as any).notify.success('Done');
}

Animation

  • Entry: 300ms cubic-bezier(0.34, 1.56, 0.64, 1) — bouncy slide from right
  • Exit: 250ms ease-in — slide right with scale(0.95) and fade

Accessibility

  • aria-live="polite" on the stack container for screen readers
  • Close button has aria-label="Dismiss"
  • Uses icons + text + color (not color alone) for status indication

Internals

  • HTML escaping via textContentinnerHTML to prevent XSS
  • Dismiss timeouts stored on the element (el._dismissTimeout) to prevent race conditions
  • el._removing flag prevents double-dismiss during animation
  • Stack capacity enforced: >5 notifications triggers auto-dismiss of the oldest
notificationstoastadminuicomponents