or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-routing.mdauthentication.mdcss-styling.mddevelopment-tools.mdform-handling.mdhtml-components.mdhtmx-integration.mdindex.mdjavascript-integration.mdnotifications.mdsvg-components.md

notifications.mddocs/

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

```