0
# Notifications and Toast Messages
1
2
User notification system with toast messages, flash messages, and session-based notification management for enhanced user experience.
3
4
## Capabilities
5
6
### Toast Message Creation
7
8
Create styled toast notifications with different types and behaviors.
9
10
```python { .api }
11
def Toast(message: str, typ: str = "info", dismiss: bool = False, duration: int = 5000):
12
"""
13
Create toast notification message.
14
15
Generates a toast notification with specified type, styling,
16
and behavior options including auto-dismiss functionality.
17
18
Args:
19
message: Notification message text
20
typ: Toast type ('info', 'success', 'warning', 'error')
21
dismiss: Whether toast can be manually dismissed
22
duration: Auto-dismiss duration in milliseconds (0 = no auto-dismiss)
23
24
Returns:
25
Toast notification element with styling and behavior
26
"""
27
```
28
29
### Session-Based Toast Management
30
31
Manage toast notifications through user sessions for persistence across requests.
32
33
```python { .api }
34
def add_toast(sess, message: str, typ: str = "info", dismiss: bool = False):
35
"""
36
Add toast notification to user session.
37
38
Stores toast notification in session for display on next
39
page load or HTMX response.
40
41
Args:
42
sess: User session object
43
message: Notification message
44
typ: Toast type ('info', 'success', 'warning', 'error')
45
dismiss: Whether toast can be dismissed
46
"""
47
48
def render_toasts(sess):
49
"""
50
Render all pending toast notifications from session.
51
52
Retrieves and renders all stored toast notifications,
53
then clears them from the session.
54
55
Args:
56
sess: User session object
57
58
Returns:
59
Collection of toast notification elements
60
"""
61
```
62
63
### Application Integration
64
65
Integrate toast system into FastHTML applications with automatic rendering.
66
67
```python { .api }
68
def setup_toasts(app, duration: int = 5000):
69
"""
70
Set up toast notification system in FastHTML app.
71
72
Configures automatic toast rendering and JavaScript
73
integration for a complete notification system.
74
75
Args:
76
app: FastHTML application instance
77
duration: Default toast duration in milliseconds
78
"""
79
80
def toast_after(resp, req, sess):
81
"""
82
After-request handler for toast notifications.
83
84
Automatically adds pending toasts to responses,
85
ensuring notifications are displayed to users.
86
87
Args:
88
resp: HTTP response object
89
req: HTTP request object
90
sess: User session object
91
92
Returns:
93
Modified response with toast notifications
94
"""
95
```
96
97
## Usage Examples
98
99
### Basic Toast Notifications
100
101
```python
102
from fasthtml.common import *
103
104
app, rt = fast_app(secret_key='demo-key')
105
106
# Set up toast system
107
setup_toasts(app, duration=4000)
108
109
@rt('/')
110
def homepage():
111
return Titled("Toast Notifications Demo",
112
Container(
113
H1("Toast Notification System"),
114
P("Click the buttons below to see different types of notifications."),
115
116
Div(
117
Button(
118
"Show Info Toast",
119
hx_post="/toast/info",
120
hx_target="#toast-area",
121
hx_swap="beforeend",
122
cls="primary"
123
),
124
Button(
125
"Show Success Toast",
126
hx_post="/toast/success",
127
hx_target="#toast-area",
128
hx_swap="beforeend",
129
cls="secondary"
130
),
131
Button(
132
"Show Warning Toast",
133
hx_post="/toast/warning",
134
hx_target="#toast-area",
135
hx_swap="beforeend"
136
),
137
Button(
138
"Show Error Toast",
139
hx_post="/toast/error",
140
hx_target="#toast-area",
141
hx_swap="beforeend"
142
),
143
style="display: flex; gap: 1rem; margin: 2rem 0;"
144
),
145
146
# Toast display area
147
Div(id="toast-area", style="position: fixed; top: 1rem; right: 1rem; z-index: 1000;")
148
)
149
)
150
151
@rt('/toast/info', methods=['POST'])
152
def show_info_toast():
153
return Toast("This is an info message!", typ="info", dismiss=True)
154
155
@rt('/toast/success', methods=['POST'])
156
def show_success_toast():
157
return Toast("Operation completed successfully!", typ="success", dismiss=True)
158
159
@rt('/toast/warning', methods=['POST'])
160
def show_warning_toast():
161
return Toast("Warning: Please check your input!", typ="warning", dismiss=True)
162
163
@rt('/toast/error', methods=['POST'])
164
def show_error_toast():
165
return Toast("Error: Something went wrong!", typ="error", dismiss=True, duration=0) # No auto-dismiss for errors
166
```
167
168
### Session-Based Toast Messages
169
170
```python
171
from fasthtml.common import *
172
173
app, rt = fast_app(secret_key='demo-key')
174
setup_toasts(app)
175
176
@rt('/')
177
def form_page(request):
178
# Render any pending toasts
179
toasts = render_toasts(request.session)
180
181
return Titled("Form with Notifications",
182
Container(
183
H1("User Registration Form"),
184
185
# Display toast area
186
Div(*toasts, id="toast-container"),
187
188
Form(
189
Div(
190
Label("Username:", for_="username"),
191
Input(type="text", name="username", id="username", required=True),
192
cls="form-group"
193
),
194
Div(
195
Label("Email:", for_="email"),
196
Input(type="email", name="email", id="email", required=True),
197
cls="form-group"
198
),
199
Div(
200
Label("Password:", for_="password"),
201
Input(type="password", name="password", id="password", required=True),
202
cls="form-group"
203
),
204
Button("Register", type="submit"),
205
method="post",
206
action="/register"
207
)
208
)
209
)
210
211
@rt('/register', methods=['POST'])
212
def register_user(username: str, email: str, password: str, request):
213
# Simulate validation
214
if len(username) < 3:
215
add_toast(request.session, "Username must be at least 3 characters long", "error")
216
return Redirect('/')
217
218
if len(password) < 6:
219
add_toast(request.session, "Password must be at least 6 characters long", "error")
220
return Redirect('/')
221
222
# Simulate checking if user exists
223
if username.lower() in ['admin', 'root', 'test']:
224
add_toast(request.session, f"Username '{username}' is already taken", "warning")
225
return Redirect('/')
226
227
# Simulate successful registration
228
add_toast(request.session, f"Welcome {username}! Your account has been created successfully.", "success")
229
add_toast(request.session, "Please check your email to verify your account.", "info")
230
231
return Redirect('/dashboard')
232
233
@rt('/dashboard')
234
def dashboard(request):
235
toasts = render_toasts(request.session)
236
237
return Titled("Dashboard",
238
Container(
239
Div(*toasts, id="toast-container"),
240
H1("User Dashboard"),
241
P("Welcome to your dashboard!"),
242
243
Div(
244
Button(
245
"Save Settings",
246
hx_post="/save-settings",
247
hx_target="#toast-container",
248
hx_swap="innerHTML"
249
),
250
Button(
251
"Delete Account",
252
hx_post="/delete-account",
253
hx_target="#toast-container",
254
hx_swap="innerHTML",
255
hx_confirm="Are you sure you want to delete your account?"
256
)
257
)
258
)
259
)
260
261
@rt('/save-settings', methods=['POST'])
262
def save_settings(request):
263
add_toast(request.session, "Settings saved successfully!", "success")
264
return render_toasts(request.session)
265
266
@rt('/delete-account', methods=['POST'])
267
def delete_account(request):
268
add_toast(request.session, "Account deletion failed. Please contact support.", "error")
269
return render_toasts(request.session)
270
```
271
272
### Advanced Toast System with Custom Styling
273
274
```python
275
from fasthtml.common import *
276
277
app, rt = fast_app(secret_key='demo-key')
278
279
# Custom toast setup with styling
280
def custom_toast_setup():
281
toast_styles = Style("""
282
.toast-container {
283
position: fixed;
284
top: 1rem;
285
right: 1rem;
286
z-index: 1000;
287
max-width: 400px;
288
}
289
290
.toast {
291
margin-bottom: 0.5rem;
292
padding: 0.75rem 1rem;
293
border-radius: 0.375rem;
294
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
295
display: flex;
296
align-items: center;
297
justify-content: space-between;
298
animation: slideIn 0.3s ease-out;
299
position: relative;
300
overflow: hidden;
301
}
302
303
.toast-info {
304
background-color: #dbeafe;
305
border-left: 4px solid #3b82f6;
306
color: #1e40af;
307
}
308
309
.toast-success {
310
background-color: #dcfce7;
311
border-left: 4px solid #22c55e;
312
color: #15803d;
313
}
314
315
.toast-warning {
316
background-color: #fef3c7;
317
border-left: 4px solid #f59e0b;
318
color: #92400e;
319
}
320
321
.toast-error {
322
background-color: #fee2e2;
323
border-left: 4px solid #ef4444;
324
color: #dc2626;
325
}
326
327
.toast-dismiss {
328
background: none;
329
border: none;
330
font-size: 1.2rem;
331
cursor: pointer;
332
opacity: 0.7;
333
margin-left: 1rem;
334
}
335
336
.toast-dismiss:hover {
337
opacity: 1;
338
}
339
340
.toast-progress {
341
position: absolute;
342
bottom: 0;
343
left: 0;
344
height: 3px;
345
background-color: rgba(0, 0, 0, 0.2);
346
animation: progress linear;
347
}
348
349
@keyframes slideIn {
350
from {
351
transform: translateX(100%);
352
opacity: 0;
353
}
354
to {
355
transform: translateX(0);
356
opacity: 1;
357
}
358
}
359
360
@keyframes progress {
361
from { width: 100%; }
362
to { width: 0%; }
363
}
364
365
.toast-auto-dismiss {
366
animation: slideOut 0.3s ease-in forwards;
367
animation-delay: var(--dismiss-delay, 4.7s);
368
}
369
370
@keyframes slideOut {
371
to {
372
transform: translateX(100%);
373
opacity: 0;
374
margin-bottom: -100px;
375
}
376
}
377
""")
378
379
toast_script = Script("""
380
function createToast(message, type = 'info', dismiss = true, duration = 5000) {
381
const container = document.getElementById('toast-container') ||
382
(() => {
383
const div = document.createElement('div');
384
div.id = 'toast-container';
385
div.className = 'toast-container';
386
document.body.appendChild(div);
387
return div;
388
})();
389
390
const toast = document.createElement('div');
391
toast.className = `toast toast-${type}`;
392
393
const messageSpan = document.createElement('span');
394
messageSpan.textContent = message;
395
toast.appendChild(messageSpan);
396
397
if (dismiss) {
398
const dismissBtn = document.createElement('button');
399
dismissBtn.className = 'toast-dismiss';
400
dismissBtn.innerHTML = '×';
401
dismissBtn.onclick = () => removeToast(toast);
402
toast.appendChild(dismissBtn);
403
}
404
405
if (duration > 0) {
406
const progress = document.createElement('div');
407
progress.className = 'toast-progress';
408
progress.style.animationDuration = `${duration}ms`;
409
toast.appendChild(progress);
410
411
setTimeout(() => removeToast(toast), duration);
412
}
413
414
container.appendChild(toast);
415
return toast;
416
}
417
418
function removeToast(toast) {
419
toast.style.animation = 'slideOut 0.3s ease-in forwards';
420
setTimeout(() => {
421
if (toast.parentNode) {
422
toast.parentNode.removeChild(toast);
423
}
424
}, 300);
425
}
426
427
// Auto-remove toasts with auto-dismiss class
428
document.addEventListener('DOMContentLoaded', function() {
429
document.querySelectorAll('.toast-auto-dismiss').forEach(toast => {
430
const duration = parseInt(toast.dataset.duration) || 5000;
431
setTimeout(() => removeToast(toast), duration);
432
});
433
});
434
""")
435
436
return [toast_styles, toast_script]
437
438
def CustomToast(message: str, typ: str = "info", dismiss: bool = True, duration: int = 5000):
439
"""Custom toast with enhanced styling and behavior."""
440
toast_class = f"toast toast-{typ}"
441
if duration > 0:
442
toast_class += " toast-auto-dismiss"
443
444
elements = [Span(message)]
445
446
if dismiss:
447
elements.append(
448
Button("×",
449
cls="toast-dismiss",
450
onclick="removeToast(this.parentElement)")
451
)
452
453
if duration > 0:
454
elements.append(
455
Div(cls="toast-progress",
456
style=f"animation-duration: {duration}ms;")
457
)
458
459
return Div(*elements,
460
cls=toast_class,
461
data_duration=str(duration) if duration > 0 else None)
462
463
@rt('/')
464
def advanced_toast_demo():
465
return Html(
466
Head(
467
Title("Advanced Toast System"),
468
Meta(charset="utf-8"),
469
Meta(name="viewport", content="width=device-width, initial-scale=1"),
470
*custom_toast_setup()
471
),
472
Body(
473
Container(
474
H1("Advanced Toast Notification System"),
475
476
Div(
477
Button(
478
"Custom Info Toast",
479
onclick="createToast('This is a custom info message!', 'info', true, 4000)"
480
),
481
Button(
482
"Persistent Error Toast",
483
onclick="createToast('This error stays until dismissed!', 'error', true, 0)"
484
),
485
Button(
486
"Quick Success Toast",
487
onclick="createToast('Quick success!', 'success', false, 2000)"
488
),
489
Button(
490
"Warning with Long Message",
491
onclick="createToast('This is a longer warning message that demonstrates how the toast system handles extended content gracefully.', 'warning', true, 6000)"
492
),
493
style="display: flex; flex-direction: column; gap: 1rem; max-width: 300px;"
494
),
495
496
# Server-side toast examples
497
Div(
498
H2("Server-Side Toasts"),
499
Button(
500
"HTMX Success Toast",
501
hx_post="/custom-toast/success",
502
hx_target="#toast-container",
503
hx_swap="beforeend"
504
),
505
Button(
506
"HTMX Error Toast",
507
hx_post="/custom-toast/error",
508
hx_target="#toast-container",
509
hx_swap="beforeend"
510
),
511
style="margin-top: 2rem;"
512
)
513
),
514
515
# Toast container
516
Div(id="toast-container", cls="toast-container")
517
)
518
)
519
520
@rt('/custom-toast/success', methods=['POST'])
521
def custom_success_toast():
522
return CustomToast("Server-side success notification!", "success", True, 3000)
523
524
@rt('/custom-toast/error', methods=['POST'])
525
def custom_error_toast():
526
return CustomToast("Server-side error notification!", "error", True, 0)
527
```
528
529
### Integration with Form Validation
530
531
```python
532
from fasthtml.common import *
533
534
app, rt = fast_app(secret_key='demo-key')
535
setup_toasts(app)
536
537
@rt('/')
538
def validation_form(request):
539
toasts = render_toasts(request.session)
540
541
return Titled("Form Validation with Toasts",
542
Container(
543
Div(*toasts, id="toast-container"),
544
545
H1("User Profile Form"),
546
547
Form(
548
Div(
549
Label("Full Name:", for_="name"),
550
Input(type="text", name="name", id="name", required=True),
551
cls="form-group"
552
),
553
Div(
554
Label("Email:", for_="email"),
555
Input(type="email", name="email", id="email", required=True),
556
cls="form-group"
557
),
558
Div(
559
Label("Age:", for_="age"),
560
Input(type="number", name="age", id="age", min="18", max="100"),
561
cls="form-group"
562
),
563
Div(
564
Label("Bio:", for_="bio"),
565
Textarea(name="bio", id="bio", rows="4", maxlength="500"),
566
cls="form-group"
567
),
568
Button("Save Profile", type="submit"),
569
hx_post="/validate-profile",
570
hx_target="#toast-container",
571
hx_swap="innerHTML"
572
)
573
)
574
)
575
576
@rt('/validate-profile', methods=['POST'])
577
def validate_profile(name: str, email: str, age: int = None, bio: str = "", request):
578
errors = []
579
warnings = []
580
581
# Validation logic
582
if len(name.strip()) < 2:
583
errors.append("Name must be at least 2 characters long")
584
585
if not email or '@' not in email:
586
errors.append("Please provide a valid email address")
587
588
if age is not None:
589
if age < 18:
590
errors.append("You must be at least 18 years old")
591
elif age > 100:
592
warnings.append("Please verify your age is correct")
593
594
if len(bio) > 500:
595
errors.append("Bio must be 500 characters or less")
596
elif len(bio) < 10 and bio:
597
warnings.append("Consider adding more details to your bio")
598
599
# Create response toasts
600
toasts = []
601
602
if errors:
603
for error in errors:
604
toasts.append(Toast(error, "error", dismiss=True, duration=0))
605
elif warnings:
606
for warning in warnings:
607
toasts.append(Toast(warning, "warning", dismiss=True, duration=8000))
608
toasts.append(Toast("Profile saved with warnings", "success", dismiss=True))
609
else:
610
toasts.append(Toast("Profile saved successfully!", "success", dismiss=True))
611
if not bio:
612
toasts.append(Toast("Tip: Add a bio to complete your profile", "info", dismiss=True))
613
614
return toasts
615
```