From litestar
Provides Litestar HTMX integration: HTMXPlugin, HTMXRequest, HTMXTemplate, and server-driven HTMX responses (TriggerEvent, ClientRedirect, etc.). Use for partial HTML endpoints, not full SPA routing.
How this skill is triggered — by the user, by Claude, or both
Slash command
/litestar:litestar-htmxThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Litestar has first-party HTMX support in `litestar.plugins.htmx`. It exposes `HTMXPlugin`, `HTMXRequest` (request-side helpers), `HTMXTemplate` (template response with HTMX headers), and HTMX-specific response objects (`TriggerEvent`, `Reswap`, `Retarget`, `PushUrl`, `HXLocation`, `ClientRedirect`, `ClientRefresh`).
Litestar has first-party HTMX support in litestar.plugins.htmx. It exposes HTMXPlugin, HTMXRequest (request-side helpers), HTMXTemplate (template response with HTMX headers), and HTMX-specific response objects (TriggerEvent, Reswap, Retarget, PushUrl, HXLocation, ClientRedirect, ClientRefresh).
This skill is Litestar-specific. For generic HTMX hx-* attributes and patterns that aren't Litestar-bound, refer directly to https://htmx.org/docs/.
T | None, never Optional[T]from __future__ import annotationsasync defHTMXRequest is a Request subclass with HTMX-aware properties:
from litestar import get
from litestar.plugins.htmx import HTMXRequest
@get("/items")
async def list_items(request: HTMXRequest) -> ...:
if request.htmx: # True if HX-Request header present
... # return partial
else:
... # full page
# Other helpers
request.htmx.target # HX-Target header (str | None)
request.htmx.trigger # HX-Trigger header
request.htmx.trigger_name # HX-Trigger-Name header
request.htmx.boosted # HX-Boosted (bool)
request.htmx.current_url # HX-Current-URL
request.htmx.history_restore_request
request.htmx.prompt # HX-Prompt (user input from hx-prompt)
Wire it into the app:
from litestar import Litestar
from litestar.plugins.htmx import HTMXPlugin
app = Litestar(route_handlers=[...], plugins=[HTMXPlugin()])
Return Jinja partials from handlers:
from litestar import get
from litestar.response import Template
@get("/items")
async def list_items() -> Template:
items = await item_service.get_many()
return Template(template_name="partials/item_list.html", context={"items": items})
For HTMX-targeted endpoints, the template is a fragment (no <html> / <body>), e.g.:
<!-- partials/item_list.html -->
<ul id="item-list">
{% for item in items %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
| Response Object | Purpose |
|---|---|
TriggerEvent(name, after="receive", params={...}) | Sets HX-Trigger / HX-Trigger-After-Swap / HX-Trigger-After-Settle |
ClientRedirect(redirect_to=...) | Sets HX-Redirect — client-side hard redirect |
ClientRefresh() | Sets HX-Refresh: true |
PushUrl(push_url=...) | Sets HX-Push-Url — adds entry to browser history |
Reswap(method="outerHTML") | Sets HX-Reswap — overrides client hx-swap |
Retarget(target="#new") | Sets HX-Retarget — overrides client hx-target |
HXLocation(redirect_to=...) | Sets HX-Location — client-side soft navigation |
HXStopPolling() | Returns 286 — HTMX stops polling on this element |
Example: trigger a custom event after a successful save:
from litestar.plugins.htmx import TriggerEvent
@post("/items")
async def create_item(data: ItemCreate) -> TriggerEvent:
item = await item_service.create(data)
return TriggerEvent(
name="itemCreated",
params={"id": item.id, "name": item.name},
after="receive",
)
For a single response that updates multiple regions, render multiple fragments and use hx-swap-oob:
<!-- main response -->
<div id="form-result">Saved!</div>
<!-- OOB updates -->
<div id="notification" hx-swap-oob="true">New notification!</div>
<div id="counter" hx-swap-oob="innerHTML">42</div>
Return the combined HTML as a Template or HTMXTemplate.
Use Litestar's CSRF middleware; expose the token to templates as a <meta> tag and forward it via HTMX:
<meta name="csrf-token" content="{{ request.scope['csrf_token'] }}">
<script>
document.body.addEventListener('htmx:configRequest', (e) => {
e.detail.headers['X-CSRF-Token'] =
document.querySelector('meta[name="csrf-token"]').content;
});
</script>
litestar-vite (template mode)For HTMX projects with bundled JS/CSS and HMR, use litestar-vite in template mode. Vite bundles HTMX + extensions + CSS; Litestar returns partials. See ../litestar-vite/SKILL.md and ../litestar-vite/references/modes.md.
<!-- base.html.j2 -->
<!DOCTYPE html>
<html>
<head>
{{ vite_hmr() }}
{{ vite('resources/main.js') }}
</head>
<body hx-ext="litestar"> <!-- enables the Litestar client extension -->
{% block content %}{% endblock %}
</body>
</html>
hx-ext="litestar" client-side templating extensionActivating hx-ext="litestar" (on <body> or any enclosing element) unlocks client-side JSON rendering via <template> tags. When an HTMX swap uses hx-swap="json", the response body is parsed as JSON and matched against ls-* attributes on descendant <template> tags.
This lets you render JSON API responses as HTML without server-side templates — complementary to the partial-HTML pattern.
| Attribute | Purpose |
|---|---|
ls-for="item in $data" | Iterate over the JSON response array |
ls-key="item.id" | Stable key for list reconciliation |
ls-if="condition" | Render only when truthy |
ls-else | Fallback block for ls-if |
${expression} | Interpolate JS expression into text content |
:attr="expression" | Dynamic attribute binding |
$data | The raw JSON response body |
Array rendering:
<button hx-get="/api/books" hx-target="#books" hx-swap="json">Load</button>
<div id="books">
<template ls-for="book in $data" ls-key="book.id">
<article :id="`book-${book.id}`">
<h3>${book.title}</h3>
<p>${book.author} • ${book.year}</p>
</article>
</template>
</div>
Single-item rendering (properties on $data accessible directly via prototype inheritance):
<div hx-get="/api/books/1" hx-target="#book" hx-swap="json">
<template ls-if="id">
<h3>${title}</h3>
<p>${author} • ${year}</p>
<div>
<template ls-for="tag in tags">
<span>${tag}</span>
</template>
</div>
</template>
<template ls-else>
<p>Click to load…</p>
</template>
</div>
When to use this vs server-side partials:
| Case | Approach |
|---|---|
| Data shape simple, rendering trivial, already have JSON endpoint | Client ls-* templating (no HTMXTemplate) |
| Complex conditionals, auth-sensitive fields, heavy formatting | Server partials via HTMXTemplate |
| Same endpoint serving both JSON (for JS clients) and HTML (for HTMX clients) | Branch on request.htmx; return JSON always and let ls-* render it for HTMX consumers |
Both coexist in one page. The canonical jinja-htmx example in litestar-vite/examples/jinja-htmx/ demonstrates both side by side.
<!-- Trigger types -->
<button hx-get="/items" hx-target="#item-list">Load</button>
<button hx-post="/items" hx-vals='{"name":"x"}'>Create</button>
<button hx-delete="/items/1" hx-confirm="Are you sure?">Delete</button>
<!-- Triggers -->
<input hx-get="/search" hx-trigger="keyup changed delay:500ms">
<div hx-get="/updates" hx-trigger="every 5s">Polling</div>
<!-- Swaps -->
<div hx-get="/x" hx-swap="outerHTML">Replace element</div>
<div hx-get="/x" hx-swap="beforeend">Append</div>
<!-- Boost -->
<a hx-boost="true" href="/page">Boost</a>
<a hx-get="/page" hx-push-url="true">Navigate with history</a>
For full HTMX attribute reference, see https://htmx.org/reference/.
Pass request_class=HTMXRequest to Litestar(...). All handlers can now type-annotate request: HTMXRequest.
For each route, decide:
Template rendering base.html)hx-get/hx-postCluster partial routes under a sub-path like /htmx/... or differentiate by request.htmx.
Build Jinja2 partials as fragments — no <html>, no <body>. Mount your full-page templates separately.
Use TriggerEvent, Refresh, Reswap, Retarget to push behavior from the server. Avoid putting business logic in the client.
litestar-vite (optional)If the app needs bundled CSS/JS or HMR for non-HTMX assets, add litestar-vite in template or htmx mode. See ../litestar-vite/SKILL.md.
Apply Litestar Guards / middleware as usual. Include CSRF token via htmx:configRequest.
Use litestar.testing.AsyncTestClient with the HX-Request: true header to exercise partial responses. See ../litestar-testing/SKILL.md.
resp = await client.get("/items", headers={"HX-Request": "true", "HX-Target": "#item-list"})
assert "<ul" in resp.text
litestar.plugins.htmx, not generic ASGI patterns — the plugin integrates with Litestar's lifecycle, OpenAPI, and DI. Treat litestar_htmx imports as legacy project signals.HTMXPlugin() at the app level — handlers shouldn't construct HTMXRequest ad-hoc.hx-get target.Template (Litestar response) — never string-concat HTML — XSS risk and template caching benefits.TriggerEvent, Reswap, Retarget) rather than ad-hoc JS — keeps logic on the server.litestar-vite only when you need bundled assets / HMR — pure HTMX with a CDN htmx.min.js works fine without Vite.hx-swap semantics.HX-Request: true to exercise the HTMX path.Before delivering Litestar + HTMX code, verify:
HTMXPlugin() is registered on the Litestar(...) constructor<html> / <body>)Template response object used (not raw HTML strings)htmx:configRequestTriggerEvent / Reswap / Retarget (not ad-hoc JS)HX-Request: true headerlitestar-vite, mode is templateTask: Items page with an HTMX-driven create form, OOB notification, and server-triggered refresh event.
# app/domain/items/controllers.py
from litestar import Controller, get, post
from litestar.response import Template
from litestar.plugins.htmx import HTMXRequest, TriggerEvent
class ItemController(Controller):
path = "/items"
@get("/")
async def index(self) -> Template:
items = await item_service.get_many()
return Template("pages/items.html", context={"items": items})
@get("/list")
async def list_partial(self, request: HTMXRequest) -> Template:
"""Partial used by hx-get on initial load and after create."""
items = await item_service.get_many()
return Template("partials/item_list.html", context={"items": items})
@post("/")
async def create(self, data: ItemCreate) -> TriggerEvent:
item = await item_service.create(data)
return TriggerEvent(
name="itemCreated",
params={"id": item.id, "name": item.name},
after="receive",
)
<!-- pages/items.html -->
{% extends "base.html" %}
{% block content %}
<form
hx-post="/items/"
hx-target="#item-list"
hx-swap="outerHTML"
hx-on::after-request="this.reset()"
>
<input name="name" required>
<button type="submit">Add</button>
</form>
<div hx-get="/items/list" hx-trigger="itemCreated from:body" hx-swap="outerHTML">
{% include "partials/item_list.html" %}
</div>
{% endblock %}
<!-- partials/item_list.html -->
<ul id="item-list">
{% for item in items %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
# tests/test_items.py
async def test_create_item_triggers_event(client):
resp = await client.post(
"/items/",
json={"name": "Widget"},
headers={"HX-Request": "true"},
)
assert resp.status_code == 201
assert "itemCreated" in resp.headers["HX-Trigger"]
litestar-vite in template mode.npx claudepluginhub litestar-org/litestar-skills --plugin litestarSearches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Implements vector databases with Pinecone, Weaviate, Qdrant, Milvus, pgvector for semantic search, RAG, recommendations, and similarity systems. Optimizes embeddings, indexing, and hybrid search.