HTMX and Hyperscript provide a powerful combination for building dynamic web applications with minimal JavaScript. Let's explore how to use these tools effectively.
Understanding HTMX and Hyperscript
These tools offer several advantages:
- Declarative interactions
- Server-side rendering
- Minimal JavaScript
- Progressive enhancement
- Simple implementation
Basic Implementation
1. HTMX Setup
Set up basic HTMX functionality:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/htmx.org"></script>
<script src="https://unpkg.com/hyperscript.org"></script>
</head>
<body>
<button
hx-post="/api/data"
hx-trigger="click"
hx-target="#result"
hx-swap="outerHTML"
>
Load Data
</button>
<div id="result"></div>
</body>
</html>
2. Server Implementation
Create server endpoints for HTMX:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/api/data', methods=['POST'])
def get_data():
return """
<div id="result">
<h3>Dynamic Content</h3>
<p>This content was loaded dynamically!</p>
</div>
"""
@app.route('/')
def index():
return render_template('index.html')
Advanced Features
1. Dynamic Forms
Implement dynamic form handling:
<form
hx-post="/submit"
hx-target="#response"
hx-swap="outerHTML"
_="on submit show #loading until htmx:afterOnLoad"
>
<input
name="email"
type="email"
required
hx-validate="true"
>
<button type="submit">Submit</button>
<div id="loading" class="hidden">
Loading...
</div>
</form>
<div id="response"></div>
<!-- Server-side handler -->
@app.route('/submit', methods=['POST'])
def submit():
email = request.form.get('email')
if not validate_email(email):
return """
<div id="response" class="error">
Invalid email address
</div>
"""
return """
<div id="response" class="success">
Thank you for subscribing!
</div>
"""
2. Dynamic Lists
Create interactive lists:
<ul id="items">
<li
hx-delete="/items/${id}"
hx-trigger="click"
hx-confirm="Are you sure?"
_="on htmx:beforeDelete add .deleting"
class="item"
>
${item.name}
</li>
</ul>
<button
hx-post="/items/new"
hx-target="#items"
hx-swap="beforeend"
>
Add Item
</button>
Advanced Patterns
1. Infinite Scroll
Implement infinite scrolling:
<div
id="content"
hx-get="/api/items"
hx-trigger="intersect once"
hx-swap="beforeend"
_="on htmx:afterOnLoad remove @hx-trigger"
>
<!-- Initial content -->
</div>
<!-- Server-side implementation -->
@app.route('/api/items')
def get_items():
page = request.args.get('page', 1, type=int)
items = get_paginated_items(page)
if not items:
return ""
html = render_template(
'items_partial.html',
items=items,
page=page + 1
)
return html
2. Live Search
Implement real-time search:
<input
type="search"
name="q"
hx-get="/search"
hx-trigger="keyup changed delay:500ms"
hx-target="#search-results"
_="on keyup if my.value.length < 3 hide #search-results"
>
<div id="search-results"></div>
<!-- Server-side implementation -->
@app.route('/search')
def search():
query = request.args.get('q', '')
if len(query) < 3:
return ""
results = search_items(query)
return render_template(
'search_results.html',
results=results
)
Performance Optimization
1. Request Optimization
Implement efficient requests:
class RequestOptimizer {
constructor() {
this.setupIndicators();
this.setupCache();
}
setupIndicators() {
htmx.on('htmx:beforeRequest', e => {
const target = e.detail.target;
target.classList.add('loading');
});
htmx.on('htmx:afterRequest', e => {
const target = e.detail.target;
target.classList.remove('loading');
});
}
setupCache() {
htmx.defineExtension('cache-control', {
onEvent: function(name, evt) {
if (name === "htmx:beforeRequest") {
const elt = evt.detail.elt;
const cache = elt.getAttribute('hx-cache');
if (cache === "false") {
evt.detail.headers['Cache-Control'] = 'no-store';
}
}
}
});
}
}
2. Progressive Enhancement
Implement graceful degradation:
class ProgressiveEnhancer {
constructor() {
this.setupFallbacks();
}
setupFallbacks() {
if (!window.htmx) {
document.body.classList.add('no-htmx');
}
document.querySelectorAll('[hx-get]').forEach(el => {
if (!window.htmx) {
const href = el.getAttribute('hx-get');
el.setAttribute('href', href);
}
});
}
}
Error Handling
1. Error Responses
Handle errors gracefully:
<div
hx-get="/api/data"
hx-trigger="load"
hx-error-class="error"
_="on htmx:error set #error-message.innerHTML to event.detail.error"
>
<div id="error-message"></div>
</div>
<!-- Server-side error handling -->
@app.errorhandler(404)
def not_found(e):
return render_template(
'error.html',
error="Resource not found"
), 404
2. Network Issues
Handle network problems:
class NetworkHandler {
constructor() {
this.setupRetry();
this.setupOffline();
}
setupRetry() {
htmx.defineExtension('retry', {
onEvent: function(name, evt) {
if (name === "htmx:afterRequest" &&
evt.detail.failed) {
setTimeout(() => {
htmx.trigger(evt.detail.elt, 'htmx:load');
}, 1000);
}
}
});
}
setupOffline() {
window.addEventListener('offline', () => {
document.body.classList.add('offline');
});
window.addEventListener('online', () => {
document.body.classList.remove('offline');
});
}
}
Best Practices
- Progressive Enhancement
- Provide fallbacks
- Handle no-JS scenarios
- Use semantic HTML
- Maintain accessibility
- Performance
- Minimize requests
- Use appropriate triggers
- Implement caching
- Handle errors gracefully
- Security
- Validate inputs
- Sanitize outputs
- Use CSRF protection
- Handle authentication
Conclusion
HTMX and Hyperscript provide powerful tools for web development:
- Benefits
- Simpler implementation
- Less JavaScript
- Better maintainability
- Progressive enhancement
- Implementation Tips
- Use semantic HTML
- Implement proper error handling
- Consider performance
- Maintain accessibility
- Best Practices
- Follow progressive enhancement
- Handle errors gracefully
- Optimize performance
- Test thoroughly
Remember to:
- Keep it simple
- Handle errors properly
- Consider accessibility
- Test thoroughly