Notification System
Global toast notification system with typed messages, progress tracking, auto-dismiss, and update-by-ID patterns
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
| Property | Value |
|---|---|
| File | src/components/admin/NotificationSystem.astro |
| Rendered in | CosmicLayout.astro (all pages using Cosmic layout) |
| Container | #notificationStack, fixed position, z-index 10000 |
| Max visible | 5 simultaneous — oldest auto-dismissed when exceeded |
| Script type | <script is:inline> to avoid SSR hydration issues |
Notification Types
| Type | Icon | Color | Auto-Dismiss |
|---|---|---|---|
success | fa-circle-check | Green (#22c55e) | 4 seconds |
error | fa-circle-exclamation | Red (#ef4444) | 6 seconds |
warning | fa-triangle-exclamation | Amber (#f59e0b) | 5 seconds |
info | fa-circle-info | Cyan (#06b6d4) | 4 seconds |
progress | fa-spinner fa-spin | Cyan (#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
textContent→innerHTMLto prevent XSS - Dismiss timeouts stored on the element (
el._dismissTimeout) to prevent race conditions el._removingflag prevents double-dismiss during animation- Stack capacity enforced: >5 notifications triggers auto-dismiss of the oldest