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

htmx-integration.mddocs/

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

```