--- name: htmx-expert description: This skill should be used when users need help with htmx development, including implementing AJAX interactions, understanding htmx attributes (hx-get, hx-post, hx-swap, hx-target, hx-trigger), debugging htmx behavior, building hypermedia-driven applications, or following htmx best practices. Use when users ask about htmx patterns, server-side HTML responses, or transitioning from SPA frameworks to htmx. (user) --- # htmx Expert This skill provides comprehensive guidance for htmx development, the library that extends HTML to access modern browser features directly without JavaScript. ## Core Philosophy htmx represents a paradigm shift toward hypermedia-first web development. Instead of treating HTML as a presentation layer with JSON APIs, htmx extends HTML to handle AJAX requests, CSS transitions, WebSockets, and Server-Sent Events directly. Servers respond with HTML fragments, not JSON. ## When to Use This Skill - Implementing htmx attributes and interactions - Building hypermedia-driven applications - Debugging htmx request/response cycles - Converting SPA patterns to htmx approaches - Understanding htmx events and lifecycle - Configuring htmx extensions - Implementing proper security measures ## Core Attributes Reference ### HTTP Verb Attributes | Attribute | Purpose | Default Trigger | |-----------|---------|-----------------| | `hx-get` | Issue GET request | click | | `hx-post` | Issue POST request | click (form: submit) | | `hx-put` | Issue PUT request | click | | `hx-patch` | Issue PATCH request | click | | `hx-delete` | Issue DELETE request | click | ### Request Control - **hx-trigger**: Customize when requests fire - Modifiers: `changed`, `delay:Xms`, `throttle:Xms`, `once` - Special triggers: `load`, `revealed`, `every Xs` - Extended: `from:`, `target:` - **hx-include**: Include additional element values in request - **hx-params**: Filter which parameters to send (`*`, `none`, `not `, ``) - **hx-headers**: Add custom headers (JSON format) - **hx-vals**: Add values to request (JSON format) - **hx-encoding**: Set encoding (`multipart/form-data` for file uploads) ### Response Handling - **hx-target**: Where to place response content - Extended selectors: `this`, `closest `, `next `, `previous `, `find ` - **hx-swap**: How to insert content - `innerHTML` (default), `outerHTML`, `beforebegin`, `afterbegin`, `beforeend`, `afterend`, `delete`, `none` - Modifiers: `swap:Xms`, `settle:Xms`, `scroll:top`, `show:top` - **hx-select**: Select subset of response to swap - **hx-select-oob**: Select elements for out-of-band swaps ### State Management - **hx-push-url**: Push URL to browser history - **hx-replace-url**: Replace current URL in history - **hx-history**: Control history snapshot behavior - **hx-history-elt**: Specify element to snapshot ### UI Indicators - **hx-indicator**: Element to show during request (add `htmx-indicator` class) - **hx-disabled-elt**: Elements to disable during request ### Security & Control - **hx-confirm**: Show confirmation dialog before request - **hx-validate**: Enable HTML5 validation on non-form elements - **hx-disable**: Disable htmx processing on element and descendants - **hx-sync**: Coordinate requests between elements ## Implementation Patterns ### Basic AJAX Pattern ```html
``` ### Active Search ```html
``` ### Infinite Scroll ```html
Loading more...
``` ### Polling ```html
Status: Unknown
``` ### Form Submission ```html
``` ### Out-of-Band Updates Server response can update multiple elements: ```html
Updated content
New notification!
42 ``` ### Loading Indicators ```html ``` CSS for indicators: ```css .htmx-indicator { opacity: 0; transition: opacity 200ms ease-in; } .htmx-request .htmx-indicator { opacity: 1; } ``` ## Server Response Patterns ### Return HTML Fragments Server endpoints return HTML, not JSON: ```python # Flask example @app.route('/search') def search(): q = request.args.get('q', '') results = search_database(q) return render_template('_search_results.html', results=results) ``` ### Response Headers htmx recognizes special headers: | Header | Purpose | |--------|---------| | `HX-Location` | Client-side redirect (with context) | | `HX-Push-Url` | Push URL to history | | `HX-Redirect` | Full page redirect | | `HX-Refresh` | Refresh the page | | `HX-Reswap` | Override hx-swap value | | `HX-Retarget` | Override hx-target value | | `HX-Trigger` | Trigger client-side events | | `HX-Trigger-After-Settle` | Trigger after settle | | `HX-Trigger-After-Swap` | Trigger after swap | ### Detect htmx Requests Check `HX-Request` header to differentiate htmx from regular requests: ```python if request.headers.get('HX-Request'): return render_template('_partial.html') else: return render_template('full_page.html') ``` ## Events ### Key Events | Event | When Fired | |-------|------------| | `htmx:load` | Element loaded into DOM | | `htmx:configRequest` | Before request sent (modify params/headers) | | `htmx:beforeRequest` | Before AJAX request | | `htmx:afterRequest` | After AJAX request completes | | `htmx:beforeSwap` | Before content swap | | `htmx:afterSwap` | After content swap | | `htmx:afterSettle` | After DOM settles | | `htmx:confirm` | Before confirmation dialog | | `htmx:validation:validate` | Custom validation hook | ### Event Handling Using `hx-on*`: ```html ``` Using JavaScript: ```javascript document.body.addEventListener('htmx:configRequest', function(evt) { evt.detail.headers['X-Custom-Header'] = 'value'; }); ``` ## Security Best Practices 1. **Escape All User Content**: Prevent XSS through server-side template escaping 2. **Use hx-disable**: Prevent htmx processing on untrusted content 3. **Restrict Request Origins**: ```javascript htmx.config.selfRequestsOnly = true; ``` 4. **Disable Script Processing**: ```javascript htmx.config.allowScriptTags = false; ``` 5. **Include CSRF Tokens**: ```html ``` 6. **Content Security Policy**: Layer browser-level protections ## Configuration Key `htmx.config` options: ```javascript htmx.config.defaultSwapStyle = 'innerHTML'; htmx.config.timeout = 0; // Request timeout (0 = none) htmx.config.historyCacheSize = 10; htmx.config.globalViewTransitions = false; htmx.config.scrollBehavior = 'instant'; // or 'smooth', 'auto' htmx.config.selfRequestsOnly = false; htmx.config.allowScriptTags = true; htmx.config.allowEval = true; ``` Or via meta tag: ```html ``` ## Extensions ### Loading Extensions ```html ``` ### Common Extensions - **head-support**: Merge head tag information across requests - **idiomorph**: Morphing swaps (preserves element state) - **sse**: Server-Sent Events support - **ws**: WebSocket support - **preload**: Content preloading - **response-targets**: HTTP status-based targeting ## Debugging Enable logging: ```javascript htmx.logAll(); ``` Check request headers in Network tab: - `HX-Request: true` - `HX-Target: ` - `HX-Trigger: ` - `HX-Current-URL: ` ## Progressive Enhancement Structure for graceful degradation: ```html
``` Non-JavaScript users get form submission; JavaScript users get AJAX. ## Third-Party Integration Initialize libraries on htmx-loaded content: ```javascript htmx.onLoad(function(content) { content.querySelectorAll('.datepicker').forEach(el => { new Datepicker(el); }); }); ``` For programmatically added htmx content: ```javascript htmx.process(document.getElementById('new-content')); ``` ## Common Gotchas 1. **ID Stability**: Keep element IDs stable for CSS transitions 2. **Swap Timing**: Default 0ms swap delay; use `swap:100ms` for transitions 3. **Event Bubbling**: htmx events bubble; use `event.detail` for data 4. **Form Data**: Only named inputs are included in requests 5. **History**: History snapshots store innerHTML, not full DOM state ## Development Environment Requirements ### htmx Requires HTTP (Not file://) htmx will NOT work when opening HTML files directly from the filesystem (`file://` URLs). This causes `htmx:invalidPath` errors because: - Browsers block cross-origin requests from `file://` URLs - htmx needs to make HTTP requests to endpoints **Solution**: Always serve htmx applications via HTTP server: ```bash # Simple Python server (recommended for development) python3 -m http.server 8000 # Or create a custom server with API endpoints python3 server.py ``` ### Minimal Development Server Pattern For htmx examples and prototypes, create a simple Python server that: 1. Serves static files (HTML, CSS, JS) 2. Provides API endpoints that return HTML fragments ```python from http.server import HTTPServer, SimpleHTTPRequestHandler from urllib.parse import urlparse, parse_qs class HtmxHandler(SimpleHTTPRequestHandler): def do_GET(self): path = urlparse(self.path).path if path.startswith("/api/"): # Return HTML fragment self.send_response(200) self.send_header("Content-Type", "text/html") self.end_headers() self.wfile.write(b"
Response HTML
") else: # Serve static files super().do_GET() HTTPServer(("", 8000), HtmxHandler).serve_forever() ``` ## Practical Implementation Lessons ### Loading Indicators with CSS Spinner Use CSS-only spinners instead of image files for better performance: ```html ``` ### Input Search with Proper Trigger Use `input changed` instead of `keyup changed` for better UX (catches paste, autofill): ```html ``` The `search` trigger handles the search input's clear button (X). ### Self-Targeting with Polling For elements that replace themselves (polling), use `hx-target="this"`: ```html
Loading...
``` ### Row Updates with closest For list items where each row has its own update button: ```html
  • Item 1
  • ``` Server returns complete `
  • ` element with new htmx attributes intact. ### Event Attribute Syntax The `hx-on::` syntax uses double colons for htmx events: ```html ``` ## Additional Resources For detailed reference, consult: - Official docs: https://htmx.org/docs/ - Attributes reference: https://htmx.org/reference/ - Examples: https://htmx.org/examples/