Test Addition: - Created tests/mjs/29-pdf-toast-notifications.test.mjs (22 assertions) - Validates toast elements, JavaScript API, animations - Tests both PDF download scenarios (modal stays open vs. closes early) - Verifies toast lifecycle: prepare → progress → success → dismiss - Tests icon/title/message updates and progress bar animation Test Coverage: ✅ Toast DOM elements exist ✅ JavaScript functions available (showPDFToast, hidePDFToast) ✅ Manual toast trigger and auto-hide ✅ PDF modal integration - Scenario A (stay in modal) ✅ PDF modal integration - Scenario B (close modal, toast appears) ✅ Toast content updates (📥 → ✅) ✅ Progress bar animation ✅ CSS animations defined (toastSlideIn, toastLifecycle, progressShrink) Documentation Addition: - Created doc/13-TOAST-NOTIFICATIONS.md (comprehensive guide) - Architecture and component structure - User experience flows (2 scenarios documented) - JavaScript API reference (showPDFToast, hidePDFToast, showError) - CSS structure and animations - Template structure with ARIA accessibility - PDF download integration details - Accessibility features and testing guide - Customization guide and troubleshooting Documentation Updates: - Updated doc/README.md (added entry #13, total: 12 → 13 docs) - Updated tests/README.md (test count: 27 → 28) - Updated tests/TEST-SUMMARY.md (added toast notifications coverage) - Updated tests/mjs/README.md (added test 29 description) Test Count: 28 active tests Doc Count: 13 core documents Coverage: Complete UX feedback system with dual-mode operation
19 KiB
Toast Notification System
Comprehensive documentation for the CV project's toast notification system.
Table of Contents
- Overview
- Architecture
- User Experience Flows
- Implementation Details
- JavaScript API
- CSS Structure
- Template Structure
- Integration with PDF Downloads
- Accessibility
- Testing
- Customization Guide
- Troubleshooting
Overview
The toast notification system provides non-intrusive visual feedback for asynchronous operations, particularly PDF generation and downloads. It enhances user experience by:
- Immediate Feedback: Users know their action was registered
- Progress Tracking: Visual indication of operation duration
- Completion Confirmation: Clear success/error messaging
- Non-Blocking UX: Users can continue working while operations complete
Key Features
✅ Multiple toast variants (success, error, info, warning) ✅ Animated slide-in/slide-out ✅ Progress bar with timing animation ✅ Auto-dismiss with configurable duration ✅ Manual close button ✅ Mobile responsive (bottom-center on small screens) ✅ Accessibility compliant (ARIA labels, reduced motion support) ✅ Bilingual support (English/Spanish)
Architecture
Component Structure
Toast System
│
├── CSS Layer (static/css/04-interactive/_toasts.css)
│ ├── Toast container positioning
│ ├── Variant styles (success, error, info, warning)
│ ├── Animations (slide, fade, progress)
│ └── Responsive breakpoints
│
├── HTML Templates (templates/partials/widgets/)
│ ├── error-toast.html (Error notifications)
│ └── pdf-toast.html (PDF download feedback)
│
├── JavaScript API (static/js/main.js)
│ ├── showPDFToast(options)
│ ├── hidePDFToast()
│ └── showError(message)
│
└── Integration Layer
└── PDF Modal (templates/partials/modals/pdf-modal.html)
└── downloadPDF() function
Design Principles
- Progressive Enhancement: Works without JavaScript (fallback to modal only)
- Separation of Concerns: CSS handles animations, JS handles logic
- Reusability: Generic toast system extensible for any notification type
- Performance: Minimal DOM manipulation, CSS-driven animations
- Accessibility: Screen reader announcements, keyboard navigation
User Experience Flows
Flow 1: PDF Download (Modal Stays Open)
User clicks "Download PDF"
↓
Modal shows loading overlay
"⏳ Preparing PDF... (~4 seconds)"
↓
User waits in modal
↓
PDF downloads to browser
↓
Modal auto-closes
↓
DONE ✅
When to use: Default behavior for users who want to watch progress.
Flow 2: PDF Download (User Closes Modal)
User clicks "Download PDF"
↓
Modal shows loading overlay
↓
User presses ESC or clicks X
↓
Toast slides in (bottom-right)
"📥 Preparing PDF..."
"Generating Default CV (5 pages)... (~4s)"
[Progress bar animates]
↓
After estimated time (~4s)
↓
Toast updates
"✅ PDF Ready!"
"Check your downloads folder"
↓
Toast auto-dismisses (3s)
↓
DONE ✅
When to use: Power users who want to continue working while PDF generates.
Implementation Details
File Structure
cv/
├── static/css/04-interactive/
│ └── _toasts.css # Toast styles (242 lines)
├── templates/partials/widgets/
│ ├── error-toast.html # Error notification template
│ └── pdf-toast.html # PDF download notification template
├── templates/index.html # Includes toast templates
├── static/js/main.js # Toast functions
└── templates/partials/modals/
└── pdf-modal.html # Integrates toast with downloads
CSS Organization
File: static/css/04-interactive/_toasts.css
Sections:
- Base Toast Container - Positioning, layout, common styles
- Toast Variants - Color schemes for different notification types
- Content Elements - Icon, title, message, close button
- Progress Bar - Visual countdown indicator
- Animations - Slide-in, slide-out, lifecycle, progress
- Responsive Design - Mobile breakpoints
- Accessibility - Reduced motion preferences
- Print Styles - Hide toasts in print media
JavaScript API
showPDFToast(options)
Display a PDF download toast notification.
Parameters:
{
icon: string, // Emoji icon (📥, ✅, ⚠️)
title: string, // Toast title
message: string, // Toast message
duration: number, // Auto-hide duration in ms (default: 5000)
autoHide: boolean // Auto-dismiss after duration (default: true)
}
Example:
window.showPDFToast({
icon: '📥',
title: 'Preparing PDF...',
message: 'Generating Default CV (5 pages)... (~4s)',
duration: 4000,
autoHide: false // Manually control dismissal
});
Return: None
Side Effects:
- Adds
.showclass to toast element - Starts progress bar animation
- Schedules auto-hide timeout (if
autoHide: true)
hidePDFToast()
Immediately hide the PDF toast.
Parameters: None
Example:
window.hidePDFToast();
Return: None
Side Effects:
- Removes
.showclass from toast element - Cancels progress bar animation
showError(message)
Display an error toast notification.
Parameters:
message: string // Error message to display
Example:
window.showError('Failed to load content. Please try again.');
Return: None
Side Effects:
- Shows error toast with red styling
- Auto-hides after 5 seconds (CSS animation)
CSS Structure
Toast Variants
Success Toast (Green)
.success-toast {
background: linear-gradient(135deg,
rgba(40, 167, 69, 0.95) 0%,
rgba(25, 135, 84, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
Error Toast (Red)
.error-toast {
background: linear-gradient(135deg,
rgba(220, 53, 69, 0.95) 0%,
rgba(200, 35, 51, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
Info Toast (Blue)
.info-toast {
background: linear-gradient(135deg,
rgba(13, 110, 253, 0.95) 0%,
rgba(10, 88, 202, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
Warning Toast (Yellow)
.warning-toast {
background: linear-gradient(135deg,
rgba(255, 193, 7, 0.95) 0%,
rgba(255, 167, 38, 0.95) 100%);
color: #333;
border-left: 4px solid #333;
}
Key Animations
Slide In
@keyframes toastSlideIn {
from {
opacity: 0;
transform: translateX(100%) translateY(0);
}
to {
opacity: 1;
transform: translateX(0) translateY(0);
}
}
Lifecycle (Auto-hide)
@keyframes toastLifecycle {
0% {
opacity: 1;
transform: translateX(0) translateY(0);
}
85% {
opacity: 1;
transform: translateX(0) translateY(0);
}
100% {
opacity: 0;
transform: translateX(100%) translateY(0);
}
}
Progress Bar
@keyframes progressShrink {
from { width: 100%; }
to { width: 0%; }
}
Template Structure
PDF Toast Template
File: templates/partials/widgets/pdf-toast.html
<div id="pdf-toast" class="success-toast no-print"
role="status" aria-live="polite" aria-atomic="true">
<!-- Icon -->
<span class="toast-icon" id="pdf-toast-icon">📥</span>
<!-- Content -->
<div class="toast-content">
<p class="toast-title" id="pdf-toast-title">Preparing PDF</p>
<p class="toast-message" id="pdf-toast-message"></p>
</div>
<!-- Close Button -->
<button aria-label="Close notification"
class="toast-close"
onclick="document.getElementById('pdf-toast').classList.remove('show')">
×
</button>
<!-- Progress Bar -->
<div class="toast-progress">
<div class="toast-progress-bar" id="pdf-toast-progress"></div>
</div>
</div>
Key Elements
role="status": ARIA role for status messagesaria-live="polite": Screen reader announcement (non-interrupting)aria-atomic="true": Read entire toast as single unit.no-print: Hidden in print media#pdf-toast-icon: Dynamically updated emoji#pdf-toast-title: Main heading#pdf-toast-message: Detailed message.toast-progress-bar: Visual countdown indicator
Integration with PDF Downloads
Download Flow Integration
File: templates/partials/modals/pdf-modal.html
function downloadPDF() {
// ... setup code ...
// Track if modal is closed by user
let modalClosedByUser = false;
const onModalClose = () => {
modalClosedByUser = true;
// Show toast when user closes modal
if (window.showPDFToast) {
window.showPDFToast({
icon: '📥',
title: isSpanish ? 'Preparando PDF...' : 'Preparing PDF...',
message: `Generating ${formatName}... (~${estimatedTime}s)`,
duration: estimatedTime * 1000,
autoHide: false
});
}
};
// Listen for modal close
modal.addEventListener('close', onModalClose, { once: true });
// Trigger download
window.location.href = url;
// After estimated time: update toast or close modal
setTimeout(() => {
if (modalClosedByUser && window.showPDFToast) {
// Update toast to success
window.showPDFToast({
icon: '✅',
title: isSpanish ? '¡PDF Listo!' : 'PDF Ready!',
message: isSpanish
? 'Revisa tu carpeta de descargas'
: 'Check your downloads folder',
duration: 3000,
autoHide: true
});
} else {
// Close modal if still open
modal.close();
}
}, estimatedTime * 1000);
}
Format-Specific Timing
| PDF Format | Pages | Estimated Time | Icon Progression |
|---|---|---|---|
| Short | 4 | 3 seconds | 📥 → ✅ |
| Default | 5 | 4 seconds | 📥 → ✅ |
| Extended | 9 | 8 seconds | 📥 → ✅ |
Accessibility
ARIA Attributes
<div id="pdf-toast"
role="status" <!-- Status message role -->
aria-live="polite" <!-- Non-interrupting announcement -->
aria-atomic="true"> <!-- Read as single unit -->
Screen Reader Behavior
- Toast Appears: Screen reader announces title + message
- Toast Updates: New content announced automatically
- Toast Dismisses: Silent (user already informed)
Keyboard Navigation
- Close Button: Focusable with
Tab - Enter/Space: Activate close button
- ESC: Closes parent modal (which may trigger toast)
Reduced Motion
Users with motion sensitivity get simplified animations:
@media (prefers-reduced-motion: reduce) {
.success-toast,
.error-toast,
.toast {
animation: none;
opacity: 1;
}
.toast-progress-bar {
animation: none;
}
}
Effect: Toast appears/disappears instantly without sliding/fading.
Testing
Automated Test
File: tests/mjs/29-pdf-toast-notifications.test.mjs
Coverage:
- ✅ Toast elements exist in DOM
- ✅ Toast initially hidden
- ✅ JavaScript functions available
- ✅ Manual toast trigger works
- ✅ Toast auto-hides after duration
- ✅ PDF modal integration (Scenario A: Stay in modal)
- ✅ PDF modal integration (Scenario B: Close modal early)
- ✅ CSS animations defined
- ✅ Icon/title/message updates
- ✅ Progress bar animation
Run Test:
bun tests/mjs/29-pdf-toast-notifications.test.mjs
Manual Testing Checklist
Scenario 1: Modal Overlay Only
- Open PDF modal
- Select Default CV
- Click Download PDF
- Wait in modal without closing
- Verify overlay shows with spinner
- Verify modal closes after ~4 seconds
- Verify NO toast appears
Scenario 2: Toast After Modal Close
- Open PDF modal
- Select Extended CV (9 pages)
- Click Download PDF
- Immediately press ESC to close modal
- Verify toast appears bottom-right
- Verify icon is 📥
- Verify title says "Preparing PDF..."
- Verify message includes format name and time
- Verify progress bar animates (shrinks)
- Wait ~8 seconds
- Verify toast updates to ✅
- Verify title says "PDF Ready!"
- Verify toast auto-dismisses after ~3 seconds
Scenario 3: Close Button
- Trigger toast manually (browser console):
showPDFToast({icon: '🧪', title: 'Test', message: 'Test message'}) - Click × close button
- Verify toast disappears immediately
Scenario 4: Mobile Responsive
- Resize browser to < 540px width
- Trigger toast
- Verify toast is full-width at bottom
- Verify content is readable
- Verify close button accessible
Customization Guide
Adding a New Toast Variant
1. Define CSS Class (static/css/04-interactive/_toasts.css):
.custom-toast {
background: linear-gradient(135deg,
rgba(156, 39, 176, 0.95) 0%,
rgba(123, 31, 162, 0.95) 100%);
color: white;
border-left: 4px solid #fff;
}
2. Create HTML Template (templates/partials/widgets/custom-toast.html):
{{define "custom-toast"}}
<div id="custom-toast" class="custom-toast no-print"
role="status" aria-live="polite">
<span class="toast-icon" id="custom-toast-icon">🎨</span>
<div class="toast-content">
<p class="toast-title" id="custom-toast-title">Custom Title</p>
<p class="toast-message" id="custom-toast-message"></p>
</div>
<button class="toast-close">×</button>
</div>
{{end}}
3. Add JavaScript Function (static/js/main.js):
window.showCustomToast = function(options = {}) {
const toast = document.getElementById('custom-toast');
const icon = document.getElementById('custom-toast-icon');
const title = document.getElementById('custom-toast-title');
const message = document.getElementById('custom-toast-message');
if (options.icon) icon.textContent = options.icon;
if (options.title) title.textContent = options.title;
if (options.message) message.textContent = options.message;
toast.classList.remove('show');
void toast.offsetWidth;
toast.classList.add('show');
if (options.autoHide !== false) {
const duration = options.duration || 5000;
setTimeout(() => toast.classList.remove('show'), duration);
}
};
4. Include in Index (templates/index.html):
{{template "custom-toast" .}}
Changing Animation Duration
CSS (_toasts.css):
/* Change lifecycle duration (default: 5s) */
.toast.show {
animation: toastLifecycle 8s ease forwards; /* Now 8 seconds */
}
/* Change progress bar duration */
.toast-progress-bar {
animation: progressShrink 8s linear forwards; /* Match lifecycle */
}
JavaScript (when calling function):
window.showPDFToast({
icon: '📥',
title: 'Preparing...',
message: 'This will take longer',
duration: 10000 // 10 seconds
});
Troubleshooting
Toast Not Appearing
Symptom: Calling showPDFToast() has no effect.
Checks:
- Verify toast element exists:
document.getElementById('pdf-toast') - Check console for JavaScript errors
- Verify CSS is loaded: Inspect element and check for
.success-toaststyles - Confirm
toasts.cssimported inmain.css - Check
index.htmlincludes{{template "pdf-toast" .}}
Solution:
# Verify CSS import
grep "toasts.css" static/css/main.css
# Verify template inclusion
grep "pdf-toast" templates/index.html
# Check browser console
# Should see no errors when calling showPDFToast()
Toast Not Auto-Dismissing
Symptom: Toast appears but never disappears.
Checks:
- Verify
autoHideis not set tofalse - Check CSS animation:
toastLifecycleshould be defined - Inspect element:
animationproperty should be set - Check for
prefers-reduced-motion(disables animations)
Solution:
// Explicitly set autoHide
window.showPDFToast({
icon: '✅',
title: 'Test',
message: 'Should auto-hide',
duration: 3000,
autoHide: true // Explicitly enable
});
// Or manually hide
setTimeout(() => window.hidePDFToast(), 3000);
Progress Bar Not Animating
Symptom: Progress bar appears but doesn't shrink.
Checks:
- Verify
progressShrinkanimation is defined in CSS - Check animation is applied: Inspect
#pdf-toast-progress - Confirm
prefers-reduced-motionis not active - Check
durationparameter matches animation duration
Solution:
// Ensure progress bar animation resets
const progressBar = document.getElementById('pdf-toast-progress');
if (progressBar) {
progressBar.style.animation = 'none';
void progressBar.offsetWidth; // Trigger reflow
progressBar.style.animation = 'progressShrink 5000ms linear forwards';
}
Toast Overlaps Content on Mobile
Symptom: Toast covers important content on small screens.
Checks:
- Verify responsive CSS is loaded
- Check viewport width:
< 540pxshould trigger mobile styles - Inspect
z-indexconflicts
Solution:
/* Adjust mobile positioning */
@media (max-width: 540px) {
.toast {
bottom: 1rem; /* Lower if needed */
right: 1rem;
left: 1rem;
z-index: 10000; /* Ensure on top */
}
}
Multiple Toasts Stacking
Symptom: Calling showPDFToast() multiple times creates overlapping toasts.
Root Cause: Current system uses single toast element, so multiple calls update the same toast.
Expected Behavior: Later calls replace earlier toast content.
If You Need Multiple Toasts:
- Clone toast element before showing:
function showMultipleToasts(options) {
const original = document.getElementById('pdf-toast');
const clone = original.cloneNode(true);
clone.id = `toast-${Date.now()}`;
document.body.appendChild(clone);
// Update clone content and show
// ... similar logic to showPDFToast ...
}
- Or use a toast queue system (advanced).
Related Documentation
- 2. Modern Web Techniques - HTMX patterns and hypermedia architecture
- 12. CSS Architecture - Modular CSS structure and ITCSS organization
- 3. API Reference - Backend API endpoints including PDF export
- 11. PDF Export - PDF generation with chromedp
Last Updated: 2025-11-20 Version: 1.0 Status: Production Ready Test Coverage: 29-pdf-toast-notifications.test.mjs (22 assertions)