Claude
Skills
Sign in
Back

htmx

Included with Lifetime
$97 forever

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.

Web Dev

What this skill does


# 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/[email protected]"></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
Files: 2
Size: 8.6 KB
Complexity: 19/100
Category: Web Dev

Related in Web Dev