ai-code/opencode.cfg/skills/htmx/SKILL.md
2026-03-17 13:40:32 +00:00

250 lines
7.1 KiB
Markdown

---
name: htmx
description: |
htmx gives you access to AJAX, CSS transitions, WebSockets, and Server-Sent Events
directly in HTML using attributes. It enables dynamic web UIs without writing JavaScript
by letting any element issue HTTP requests and swap content into the DOM.
license: Apache-2.0
compatibility: 'any web server, html'
metadata:
author: terminal-skills
version: 1.0.0
category: development
tags:
- html
- hypermedia
- ajax
- sse
- websocket
- no-javascript
---
# htmx
htmx extends HTML with attributes like `hx-get`, `hx-post`, `hx-swap`, and `hx-trigger` to make any element capable of issuing HTTP requests and updating the DOM. The server returns HTML fragments, not JSON.
## Installation
```html
<!-- index.html — add htmx via CDN or npm -->
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<!-- Or: npm install htmx.org -->
```
## Core Attributes
```html
<!-- templates/core-demo.html — fundamental htmx attributes -->
<!-- GET request, replace inner HTML of target -->
<button hx-get="/api/articles" hx-target="#article-list" hx-swap="innerHTML">
Load Articles
</button>
<div id="article-list"></div>
<!-- POST form without page reload -->
<form hx-post="/api/articles" hx-target="#article-list" hx-swap="afterbegin">
<input name="title" placeholder="Title" required />
<textarea name="body" placeholder="Body" required></textarea>
<button type="submit">Create</button>
</form>
<!-- DELETE with confirmation -->
<button hx-delete="/api/articles/42" hx-confirm="Are you sure?" hx-target="closest article" hx-swap="outerHTML swap:500ms">
Delete
</button>
```
## Swap Strategies
```html
<!-- templates/swap-strategies.html — different ways to insert content -->
<!-- Replace inner content (default) -->
<div hx-get="/fragment" hx-swap="innerHTML">Replace my contents</div>
<!-- Replace entire element -->
<div hx-get="/fragment" hx-swap="outerHTML">Replace me entirely</div>
<!-- Append/prepend to list -->
<div id="list">
<button hx-get="/more" hx-target="#list" hx-swap="beforeend">Load More</button>
</div>
<!-- Swap with transition delay -->
<div hx-get="/fragment" hx-swap="innerHTML settle:300ms">With transition</div>
<!-- Out-of-band swaps (update multiple elements) -->
<!-- Server returns: -->
<!-- <div id="notification" hx-swap-oob="innerHTML">New notification!</div> -->
<!-- <div id="count" hx-swap-oob="innerHTML">43</div> -->
```
## Triggers
```html
<!-- templates/triggers.html — custom event triggers -->
<!-- Trigger on input change with debounce -->
<input type="search" name="q"
hx-get="/search"
hx-trigger="input changed delay:300ms"
hx-target="#results" />
<div id="results"></div>
<!-- Trigger on intersection (lazy loading) -->
<div hx-get="/more-articles"
hx-trigger="intersect once"
hx-swap="afterend">
Loading...
</div>
<!-- Trigger on custom event -->
<div hx-get="/notifications" hx-trigger="newMessage from:body">
Notifications
</div>
<!-- Polling -->
<div hx-get="/api/status" hx-trigger="every 5s">
Status: checking...
</div>
```
## Server Responses (Python/Django Example)
```python
# views.py — server returns HTML fragments, not JSON
from django.shortcuts import render
from django.http import HttpResponse
def article_list(request):
articles = Article.objects.filter(published=True)[:20]
return render(request, "partials/article_list.html", {"articles": articles})
def create_article(request):
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save()
return render(request, "partials/article_card.html", {"article": article})
return render(request, "partials/article_form.html", {"form": form}, status=422)
def delete_article(request, pk):
Article.objects.filter(pk=pk).delete()
return HttpResponse("") # Empty response removes element with outerHTML swap
```
```html
<!-- templates/partials/article_card.html — HTML fragment returned by server -->
<article id="article-{{ article.id }}">
<h2>{{ article.title }}</h2>
<p>{{ article.body|truncatewords:30 }}</p>
<button hx-delete="/api/articles/{{ article.id }}"
hx-target="#article-{{ article.id }}"
hx-swap="outerHTML swap:300ms"
hx-confirm="Delete this article?">
Delete
</button>
</article>
```
## Indicators
```html
<!-- templates/indicators.html — loading indicators -->
<!-- Show spinner during request -->
<button hx-get="/slow-endpoint" hx-indicator="#spinner">
Load Data
</button>
<span id="spinner" class="htmx-indicator">Loading...</span>
<!-- CSS (htmx adds .htmx-request class during requests) -->
<style>
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline; }
.htmx-request.htmx-indicator { display: inline; }
</style>
```
## Headers and Request Config
```html
<!-- templates/request-config.html — request customization -->
<!-- Include extra values -->
<button hx-post="/api/vote" hx-vals='{"article_id": 42, "vote": "up"}'>
Upvote
</button>
<!-- Include values from other elements -->
<input id="search-input" name="q" />
<button hx-get="/search" hx-include="#search-input">Search</button>
<!-- Push URL to browser history -->
<a hx-get="/articles/my-article" hx-push-url="true" hx-target="#content">
My Article
</a>
```
## Server-Sent Events
```html
<!-- templates/sse.html — real-time updates with SSE -->
<div hx-ext="sse" sse-connect="/events/articles">
<div sse-swap="newArticle" hx-swap="afterbegin">
<!-- New articles appear here in real-time -->
</div>
</div>
```
```python
# views.py — SSE endpoint
import json
from django.http import StreamingHttpResponse
def article_events(request):
def event_stream():
for article in listen_for_new_articles():
html = render_to_string("partials/article_card.html", {"article": article})
yield f"event: newArticle\ndata: {html}\n\n"
return StreamingHttpResponse(event_stream(), content_type="text/event-stream")
```
## WebSocket
```html
<!-- templates/ws.html — WebSocket integration -->
<div hx-ext="ws" ws-connect="/ws/chat">
<div id="messages"></div>
<form ws-send>
<input name="message" placeholder="Type a message..." />
<button type="submit">Send</button>
</form>
</div>
```
## Boosting (Progressive Enhancement)
```html
<!-- templates/boost.html — make regular links/forms use AJAX -->
<body hx-boost="true">
<!-- All links and forms in this body now use AJAX -->
<nav>
<a href="/articles">Articles</a> <!-- AJAX navigation -->
<a href="/about">About</a>
</nav>
<main id="content">
<!-- Content swapped here -->
</main>
</body>
```
## Key Patterns
- Server returns HTML fragments, not JSON — this is hypermedia, not REST
- Use `hx-target` to control where responses are inserted; `hx-swap` controls how
- Use `hx-trigger` with modifiers (`delay`, `throttle`, `changed`, `once`) for precise control
- Use `hx-boost="true"` on `<body>` for easy progressive enhancement of existing sites
- Use `hx-swap-oob` for updating multiple page sections from a single response
- Use `hx-indicator` for loading states — htmx manages the CSS class automatically
- Use `hx-push-url` to update browser URL for back-button support