0
# HTMX Integration and Dynamic Interactions
1
2
Built-in HTMX support for creating dynamic, interactive web applications without writing JavaScript. FastHTML provides seamless integration with HTMX attributes, response handling, and event management.
3
4
## Capabilities
5
6
### HTMX Request Headers
7
8
Access HTMX-specific request information through structured headers dataclass.
9
10
```python { .api }
11
class HtmxHeaders:
12
"""
13
HTMX request headers dataclass.
14
15
Provides access to HTMX-specific request information.
16
"""
17
hx_request: str # Indicates HTMX request
18
hx_target: str # Target element ID or selector
19
hx_trigger: str # Element that triggered the request
20
hx_trigger_name: str # Name of the triggering element
21
hx_current_url: str # Current page URL
22
hx_history_restore_request: str # History restore request indicator
23
hx_prompt: str # User input from hx-prompt
24
hx_boosted: str # Boosted request indicator
25
```
26
27
### Enhanced HTML with HTMX
28
29
Create HTML elements with built-in HTMX functionality and attributes.
30
31
```python { .api }
32
def ft_hx(tag: str, *c, **kw):
33
"""
34
Create HTML element with HTMX support.
35
36
Automatically processes HTMX attributes and provides enhanced
37
functionality for dynamic interactions.
38
39
Args:
40
tag: HTML tag name
41
*c: Child content
42
**kw: HTML attributes including HTMX attributes
43
44
Returns:
45
HTML element with HTMX functionality
46
"""
47
```
48
49
### HTMX Response Processing
50
51
Handle HTMX-specific response requirements and content swapping.
52
53
```python { .api }
54
def is_full_page(request) -> bool:
55
"""
56
Check if response should be full page or partial.
57
58
Determines whether to return a complete HTML page or
59
just the requested fragment based on HTMX headers.
60
61
Args:
62
request: HTTP request object
63
64
Returns:
65
bool: True if full page response needed
66
"""
67
68
def flat_xt(xt):
69
"""
70
Flatten XML tree structure for HTMX responses.
71
72
Args:
73
xt: XML tree or FastHTML element structure
74
75
Returns:
76
Flattened structure suitable for HTMX consumption
77
"""
78
```
79
80
### HTMX Constants and Configuration
81
82
Pre-defined HTMX constants and configuration options.
83
84
```python { .api }
85
htmx_hdrs: dict
86
"""HTMX header mappings for request processing."""
87
88
htmx_resps: dict
89
"""HTMX response type mappings."""
90
91
htmx_exts: dict
92
"""Available HTMX extensions."""
93
94
htmxsrc: str
95
"""HTMX JavaScript source URL."""
96
97
def def_hdrs(htmx: bool = True, surreal: bool = True) -> list:
98
"""
99
Get default headers with HTMX/Surreal support.
100
101
Args:
102
htmx: Include HTMX JavaScript library
103
surreal: Include Surreal.js library
104
105
Returns:
106
list: Default header elements
107
"""
108
```
109
110
## HTMX Attributes
111
112
FastHTML supports all HTMX attributes as keyword arguments. Here are the most commonly used:
113
114
### Core HTMX Attributes
115
116
```python
117
# HTTP request attributes
118
hx_get="/path" # GET request to path
119
hx_post="/path" # POST request to path
120
hx_put="/path" # PUT request to path
121
hx_patch="/path" # PATCH request to path
122
hx_delete="/path" # DELETE request to path
123
124
# Target and swapping
125
hx_target="#element-id" # Target element for response
126
hx_swap="innerHTML" # How to swap content (innerHTML, outerHTML, etc.)
127
hx_select="#selector" # Select part of response
128
129
# Triggering
130
hx_trigger="click" # Event that triggers request
131
hx_trigger="click delay:1s" # Trigger with delay
132
hx_trigger="every 5s" # Periodic trigger
133
134
# Loading states
135
hx_indicator="#spinner" # Loading indicator element
136
hx_disabled_elt="this" # Disable element during request
137
138
# Form handling
139
hx_include="#form-data" # Include additional form data
140
hx_params="*" # Which parameters to include
141
142
# History and navigation
143
hx_push_url="true" # Push URL to browser history
144
hx_replace_url="true" # Replace current URL
145
146
# Validation and confirmation
147
hx_confirm="Are you sure?" # Confirmation dialog
148
hx_validate="true" # Client-side validation
149
```
150
151
## Usage Examples
152
153
### Basic HTMX Interactions
154
155
```python
156
from fasthtml.common import *
157
158
app, rt = fast_app()
159
160
@rt('/')
161
def homepage():
162
return Titled("HTMX Demo",
163
Div(
164
H1("HTMX Integration Examples"),
165
166
# Simple GET request
167
Button(
168
"Load Content",
169
hx_get="/content",
170
hx_target="#content-area"
171
),
172
Div(id="content-area", "Content will load here"),
173
174
# POST form with HTMX
175
Form(
176
Input(type="text", name="message", placeholder="Enter message"),
177
Button("Send", type="submit"),
178
hx_post="/send-message",
179
hx_target="#messages",
180
hx_swap="afterbegin"
181
),
182
Div(id="messages")
183
)
184
)
185
186
@rt('/content')
187
def load_content():
188
return Div(
189
P("This content was loaded dynamically!"),
190
Small(f"Loaded at {datetime.now()}"),
191
style="padding: 1rem; border: 1px solid #ccc; margin: 1rem 0;"
192
)
193
194
@rt('/send-message', methods=['POST'])
195
def send_message(message: str):
196
return Div(
197
Strong("New message: "),
198
Span(message),
199
style="padding: 0.5rem; background: #e8f5e8; margin: 0.5rem 0;"
200
)
201
```
202
203
### Advanced HTMX Patterns
204
205
```python
206
from fasthtml.common import *
207
208
app, rt = fast_app()
209
210
@rt('/advanced-htmx')
211
def advanced_examples():
212
return Titled("Advanced HTMX",
213
Div(
214
# Live search with debouncing
215
Section(
216
H2("Live Search"),
217
Input(
218
type="text",
219
name="search",
220
placeholder="Search users...",
221
hx_get="/search",
222
hx_target="#search-results",
223
hx_trigger="keyup changed delay:300ms",
224
hx_indicator="#search-spinner"
225
),
226
Div(id="search-spinner", "๐", style="display: none;"),
227
Div(id="search-results")
228
),
229
230
# Infinite scroll
231
Section(
232
H2("Infinite Scroll"),
233
Div(id="content-list",
234
# Initial content
235
*[Div(f"Item {i}", cls="list-item") for i in range(10)],
236
# Load more trigger
237
Div(
238
"Loading more...",
239
hx_get="/load-more?page=2",
240
hx_target="this",
241
hx_swap="outerHTML",
242
hx_trigger="revealed"
243
)
244
)
245
),
246
247
# Modal dialog
248
Section(
249
H2("Modal Dialog"),
250
Button(
251
"Open Modal",
252
hx_get="/modal",
253
hx_target="body",
254
hx_swap="beforeend"
255
)
256
),
257
258
# Real-time updates
259
Section(
260
H2("Real-time Counter"),
261
Div(id="counter", "0"),
262
Button(
263
"Start Updates",
264
hx_get="/start-counter",
265
hx_target="#counter",
266
hx_trigger="click"
267
)
268
)
269
)
270
)
271
272
@rt('/search')
273
def search_users(search: str = ""):
274
if not search:
275
return Div("Enter search term")
276
277
# Simulate user search
278
users = [f"User {i}: {search}" for i in range(1, 6)]
279
return Div(
280
*[Div(user, cls="search-result") for user in users]
281
)
282
283
@rt('/load-more')
284
def load_more(page: int = 1):
285
start = (page - 1) * 10
286
items = [Div(f"Item {start + i}", cls="list-item") for i in range(1, 11)]
287
288
if page < 5: # Simulate having more pages
289
load_trigger = Div(
290
"Loading more...",
291
hx_get=f"/load-more?page={page + 1}",
292
hx_target="this",
293
hx_swap="outerHTML",
294
hx_trigger="revealed"
295
)
296
items.append(load_trigger)
297
298
return Div(*items)
299
300
@rt('/modal')
301
def show_modal():
302
return Div(
303
Div(
304
H3("Modal Title"),
305
P("This is modal content"),
306
Button(
307
"Close",
308
onclick="this.closest('.modal-overlay').remove()"
309
),
310
cls="modal-content"
311
),
312
cls="modal-overlay",
313
style="""
314
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
315
background: rgba(0,0,0,0.5); display: flex;
316
align-items: center; justify-content: center;
317
"""
318
)
319
320
@rt('/start-counter')
321
def start_counter():
322
return Div(
323
"1",
324
hx_get="/increment-counter?count=1",
325
hx_trigger="load delay:1s",
326
hx_swap="outerHTML"
327
)
328
329
@rt('/increment-counter')
330
def increment_counter(count: int = 0):
331
new_count = count + 1
332
if new_count <= 10:
333
return Div(
334
str(new_count),
335
hx_get=f"/increment-counter?count={new_count}",
336
hx_trigger="load delay:1s",
337
hx_swap="outerHTML"
338
)
339
return Div("Finished!")
340
```
341
342
### Form Handling with HTMX
343
344
```python
345
from fasthtml.common import *
346
347
app, rt = fast_app()
348
349
@rt('/htmx-forms')
350
def htmx_forms():
351
return Titled("HTMX Forms",
352
Div(
353
# Inline validation
354
Form(
355
H2("Registration Form"),
356
Div(
357
Label("Username:", for_="username"),
358
Input(
359
type="text",
360
name="username",
361
id="username",
362
hx_post="/validate-username",
363
hx_target="#username-validation",
364
hx_trigger="blur"
365
),
366
Div(id="username-validation"),
367
cls="form-group"
368
),
369
Div(
370
Label("Email:", for_="email"),
371
Input(
372
type="email",
373
name="email",
374
id="email",
375
hx_post="/validate-email",
376
hx_target="#email-validation",
377
hx_trigger="blur"
378
),
379
Div(id="email-validation"),
380
cls="form-group"
381
),
382
Button("Register", type="submit"),
383
hx_post="/register",
384
hx_target="#form-result"
385
),
386
Div(id="form-result"),
387
388
# Dynamic form fields
389
Form(
390
H2("Dynamic Fields"),
391
Div(id="field-container",
392
Div(
393
Input(type="text", name="field1", placeholder="Field 1"),
394
cls="dynamic-field"
395
)
396
),
397
Button(
398
"Add Field",
399
type="button",
400
hx_post="/add-field",
401
hx_target="#field-container",
402
hx_swap="beforeend"
403
),
404
Button("Submit", type="submit"),
405
hx_post="/submit-dynamic",
406
hx_target="#dynamic-result"
407
),
408
Div(id="dynamic-result")
409
)
410
)
411
412
@rt('/validate-username', methods=['POST'])
413
def validate_username(username: str):
414
if len(username) < 3:
415
return Div("Username too short", style="color: red;")
416
elif username.lower() in ['admin', 'root', 'user']:
417
return Div("Username not available", style="color: red;")
418
else:
419
return Div("Username available", style="color: green;")
420
421
@rt('/validate-email', methods=['POST'])
422
def validate_email(email: str):
423
if '@' not in email:
424
return Div("Invalid email format", style="color: red;")
425
else:
426
return Div("Email format valid", style="color: green;")
427
428
@rt('/add-field', methods=['POST'])
429
def add_field():
430
import random
431
field_id = random.randint(1000, 9999)
432
return Div(
433
Input(
434
type="text",
435
name=f"field{field_id}",
436
placeholder=f"Field {field_id}"
437
),
438
Button(
439
"Remove",
440
type="button",
441
onclick="this.closest('.dynamic-field').remove()"
442
),
443
cls="dynamic-field"
444
)
445
```
446
447
### Error Handling and Loading States
448
449
```python
450
from fasthtml.common import *
451
452
app, rt = fast_app()
453
454
@rt('/error-handling')
455
def error_handling():
456
return Titled("Error Handling",
457
Div(
458
# Loading states
459
Button(
460
"Slow Request",
461
hx_get="/slow-request",
462
hx_target="#slow-result",
463
hx_indicator="#loading-spinner",
464
hx_disabled_elt="this"
465
),
466
Div(id="loading-spinner", "Loading...", style="display: none;"),
467
Div(id="slow-result"),
468
469
# Error handling
470
Button(
471
"Request with Error",
472
hx_get="/error-request",
473
hx_target="#error-result"
474
),
475
Div(id="error-result"),
476
477
# Retry mechanism
478
Button(
479
"Unreliable Request",
480
hx_get="/unreliable-request",
481
hx_target="#retry-result"
482
),
483
Div(id="retry-result")
484
)
485
)
486
487
@rt('/slow-request')
488
def slow_request():
489
import time
490
time.sleep(2) # Simulate slow operation
491
return Div("Request completed!", style="color: green;")
492
493
@rt('/error-request')
494
def error_request():
495
# Simulate error condition
496
return Div(
497
"An error occurred!",
498
Button(
499
"Retry",
500
hx_get="/error-request",
501
hx_target="#error-result"
502
),
503
style="color: red;"
504
), 500 # HTTP 500 error
505
506
@rt('/unreliable-request')
507
def unreliable_request():
508
import random
509
if random.random() < 0.5:
510
return Div("Success!", style="color: green;")
511
else:
512
return Div(
513
"Failed, try again",
514
Button(
515
"Retry",
516
hx_get="/unreliable-request",
517
hx_target="#retry-result"
518
),
519
style="color: orange;"
520
)
521
```