AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Litestar Htmx

skill-litestar-org-litestar-skills-litestar-htmx · by litestar-org

Auto-activate for litestar.plugins.htmx, litestar_htmx, HTMXPlugin, HTMXRequest, HTMXTemplate, TriggerEvent, hx-* attributes, or partial HTML. Not for full SPA routing.

No reviews yet
0 installs
31 views
0.0% view→install

Install

$ agentstack add skill-litestar-org-litestar-skills-litestar-htmx

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-litestar-org-litestar-skills-litestar-htmx)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Litestar Htmx? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

litestar-htmx

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 .

Code Style Rules

  • PEP 604 unions: T | None, never Optional[T]
  • Consumer Litestar app modules MAY use from __future__ import annotations
  • Async all I/O — handlers are async def
  • Return partial HTML, not full pages, from HTMX-targeted endpoints
  • Use Jinja2 (or Mako) templates for partials; do not string-concat HTML

Quick Reference

HTMXRequest

HTMXRequest 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()])

HTMXTemplate + Partial HTML

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 ` / `), e.g.:


  {% for item in items %}
    {{ item.name }}
  {% endfor %}

Server-driven HTMX Responses

| 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",
    )

OOB (Out-of-Band) Swaps

For a single response that updates multiple regions, render multiple fragments and use hx-swap-oob:


Saved!

New notification!
42

Return the combined HTML as a Template or HTMXTemplate.

CSRF

Use Litestar's CSRF middleware; expose the token to templates as a `` tag and forward it via HTMX:


  document.body.addEventListener('htmx:configRequest', (e) => {
    e.detail.headers['X-CSRF-Token'] =
      document.querySelector('meta[name="csrf-token"]').content;
  });

Pairing with 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](../litestar-vite/references/modes.md#htmx--template-mode).


  {{ vite_hmr() }}
  {{ vite('resources/main.js') }}

          
  {% block content %}{% endblock %}

The hx-ext="litestar" client-side templating extension

Activating hx-ext="litestar" (on ` or any enclosing element) unlocks **client-side JSON rendering** via tags. When an HTMX swap uses hx-swap="json", the response body is parsed as JSON and matched against ls-* attributes on descendant ` 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:

Load

  
    
      ${book.title}
      ${book.author} • ${book.year}
    
  

Single-item rendering (properties on $data accessible directly via prototype inheritance):


  
    ${title}
    ${author} • ${year}
    
      
        ${tag}
      
    
  
  
    Click to load…
  

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.

Common HTMX Attributes (quick refresher)


Load
Create
Delete

Polling

Replace element
Append

Boost
Navigate with history

For full HTMX attribute reference, see .

Workflow

Step 1: Wire HTMXRequest

Pass request_class=HTMXRequest to Litestar(...). All handlers can now type-annotate request: HTMXRequest.

Step 2: Decide Page vs Partial Boundaries

For each route, decide:

  • Page route — returns full layout (one Template rendering base.html)
  • Partial route — returns a fragment used by hx-get/hx-post

Cluster partial routes under a sub-path like /htmx/... or differentiate by request.htmx.

Step 3: Templates for Partials

Build Jinja2 partials as fragments — no `, no `. Mount your full-page templates separately.

Step 4: Server-driven Behavior

Use TriggerEvent, Refresh, Reswap, Retarget to push behavior from the server. Avoid putting business logic in the client.

Step 5: Pair with 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.

Step 6: CSRF + Auth

Apply Litestar Guards / middleware as usual. Include CSRF token via htmx:configRequest.

Step 7: Test

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 "

## Guardrails

- **Use `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.
- **Register `HTMXPlugin()`** at the app level — handlers shouldn't construct `HTMXRequest` ad-hoc.
- **Return partial HTML for HTMX-targeted routes** — never return a full layout to an `hx-get` target.
- **Use `Template` (Litestar response) — never string-concat HTML** — XSS risk and template caching benefits.
- **CSRF protection applies to HTMX too** — non-GET HTMX requests must include the CSRF token (header preferred).
- **Use server-driven response objects** (`TriggerEvent`, `Reswap`, `Retarget`) rather than ad-hoc JS — keeps logic on the server.
- **Pair with `litestar-vite` only when you need bundled assets / HMR** — pure HTMX with a CDN htmx.min.js works fine without Vite.
- **Don't return JSON to HTMX endpoints** — HTMX expects HTML; JSON breaks `hx-swap` semantics.
- **Test with `HX-Request: true`** to exercise the HTMX path.

### Validation Checkpoint

Before delivering Litestar + HTMX code, verify:

- [ ] `HTMXPlugin()` is registered on the `Litestar(...)` constructor
- [ ] HTMX-targeted routes return Jinja2 fragments (no `` / ``)
- [ ] `Template` response object used (not raw HTML strings)
- [ ] CSRF middleware enabled; token forwarded via `htmx:configRequest`
- [ ] Server-driven behavior uses `TriggerEvent` / `Reswap` / `Retarget` (not ad-hoc JS)
- [ ] Tests assert against partial HTML with `HX-Request: true` header
- [ ] If using `litestar-vite`, mode is `template`

## Example

**Task:** Items page with an HTMX-driven create form, OOB notification, and server-triggered refresh event.

```python
# 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",
        )

{% extends "base.html" %}

{% block content %}
  
    
    Add
  

  
    {% include "partials/item_list.html" %}
  
{% endblock %}

  {% for item in items %}
    {{ item.name }}
  {% endfor %}
# 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"]

References Index

  • [litestar-vite Integration](references/litestar_vite.md) — Bundling HTMX + custom JS/CSS with litestar-vite in template mode.

Cross-References

  • [litestar](../litestar/SKILL.md) — Litestar fundamentals (Templates, Controllers, Guards, middleware).
  • [litestar-vite](../litestar-vite/SKILL.md) — HTMX + Jinja with Vite-bundled assets.

Official References

Shared Styleguide Baseline

  • [General Principles](../litestar-styleguide/references/general.md)
  • [Litestar](../litestar-styleguide/references/litestar.md)

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.