HTMX Cursor Rules: Hypermedia-Driven Frontend
Cursor rules for HTMX covering hx-* attributes, swap strategies, trigger modifiers, out-of-band swaps, server-sent events, progressive enhancement, and hypermedia API patterns.

Overview
HTMX is a hypermedia-driven JavaScript library that extends HTML with direct access to AJAX, CSS transitions, WebSockets, and Server-Sent Events — all through declarative attributes without writing JavaScript. These cursor rules enforce proper hx-* attribute usage, swap strategies, trigger modifiers, server-side response headers, out-of-band swaps, and progressive enhancement patterns so AI assistants generate clean, accessible hypermedia-driven frontends.
Note:
Enforces hx-get/hx-post/hx-put/hx-patch/hx-delete attributes, hx-trigger modifiers (delay, throttle, once, changed), hx-target with extended CSS selectors, hx-swap strategies, HX-Trigger response headers, out-of-band swaps, and progressive enhancement patterns.
Rules Configuration
---
description: Enforces HTMX best practices including hx-* attribute usage, trigger modifiers, extended CSS selectors for targets, swap strategies, server response headers, out-of-band swaps, and progressive enhancement. Provides guidelines for hypermedia-driven frontend development.
globs: **/*.html,**/*.htm,**/*.jsx,**/*.tsx,**/*.njk,**/*.hbs,**/*.tera,**/*.jinja,**/*.ejs
---
# HTMX Best Practices
You are an expert in HTMX, hypermedia-driven applications, and progressive enhancement patterns.
You understand declarative AJAX, server-side rendering, REST/HATEOAS principles, and multi-page application architecture.
### Core hx-* Attributes
- Use hx-get for retrieving data (search, pagination, lazy loading)
- Use hx-post for creating resources or form submissions
- Use hx-put for full resource updates
- Use hx-patch for partial updates
- Use hx-delete for resource deletions (always pair with hx-confirm)
- Prefer the non-data-prefix forms (hx-get, not data-hx-get)
- Place hx-* attributes directly on trigger elements, not wrapper divs
### Triggers (hx-trigger)
- Use natural events by default: click for buttons, change for inputs, submit for forms
- Use `delay:500ms` for search-as-you-type to avoid excessive requests
- Use `throttle:1s` for scroll/resize handlers to limit request frequency
- Use `once` modifier for one-time actions where re-triggering would be harmful
- Use `changed` modifier to prevent redundant requests on unchanged inputs
- Use `from:#other-elem` for keyboard shortcuts and cross-element triggers
- Use `every 2s` for polling (prefer SSE for real-time unless polling is required)
- Chain triggers with commas: `hx-trigger="click, keyup[key=='Enter']"`
### Targets (hx-target)
- Use `hx-target="#results"` for a specific element by ID
- Use `hx-target="this"` to replace the triggering element
- Use `hx-target="closest form"` to target the nearest parent form
- Use `hx-target="next .error-container"` for error messages next to inputs
- Use `hx-target="find .list"` for finding a descendant child
- Avoid duplicating the same target across many elements — use attribute inheritance
### Swapping (hx-swap)
- Default `innerHTML` for most content replacements
- Use `outerHTML` when the target element itself should be replaced
- Use `beforeend` for appending to lists and infinite scroll
- Use `afterbegin` for prepending new items
- Use `delete` to remove elements without server response content
- Use `ignoreTitle:true` when responses shouldn't change the page title
- Use `scroll:bottom` for chat interfaces and log viewers
- Use `settle:0` to skip the settle delay when instant rendering is needed
- Use `morph` (via idiomorph extension) when preserving DOM state
### Server Response Headers
- Set `HX-Trigger` to fire client-side events from the server
- Use `HX-Trigger: {"showToast": "Item saved"}` for toasts and notifications
- Set `HX-Retarget` to dynamically change the swap target based on server logic
- Set `HX-Reswap` to override the swap method server-side
- Set `HX-Redirect` for full-page redirects after form submissions
- Set `HX-Refresh: true` to force a full page refresh
- Only return HTML fragments from HTMX endpoints (not full pages)
- Use `HX-Request` header server-side to detect HTMX vs regular requests
### Out-of-Band Swaps
- Use `hx-swap-oob="true"` in response HTML for multi-target updates
- Always include the `id` attribute on OOB elements matching target IDs
- Use `<template>` to wrap OOB elements that aren't valid standalone HTML
- Limit OOB swaps to 2-3 per response to keep responses readable
- Prefer `HX-Trigger` + client-side handling over excessive OOB swapping
### Progressive Enhancement
- Wrap HTMX-enhanced inputs in `<form>` tags for non-JS fallback
- Use `hx-boost="true"` on navigation containers for SPA-like navigation without breaking non-JS
- Server must handle both HTMX (partial) and regular (full page) requests
- All forms should work with or without JavaScript
- Use semantic HTML elements (button, form, input) as the foundation
### Performance & Accessibility
- Use `hx-indicator` for loading states with CSS transitions
- Use `hx-disabled-elt` to disable buttons during requests
- Apply `htmx-request`, `htmx-swapping`, and `htmx-settling` CSS classes for transitions
- Set `htmx.config.requestClass` if the default `htmx-request` conflicts
- Use `hx-history="false"` on pages with sensitive data to prevent localStorage caching
- Test all pages with JavaScript disabled to verify progressive enhancement
### Inline Scripting (hx-on)
- Use `hx-on:<event>` to execute inline JavaScript on HTMX events
- Example: `hx-on:htmx:after-request="console.log('done')"`
- Prefer `hx-on:*` for simple reactions over separate script blocks
- Use `hx-on` for one-liners: showing toasts, resetting forms, focusing inputs
- Event object available as `event` in the expression scope
### Event System
- Listen to HTMX events on `document.body`: `htmx:configRequest`, `htmx:beforeSwap`, `htmx:afterSwap`, `htmx:responseError`
- `htmx:configRequest` — modify request parameters, add headers, change method
- `htmx:beforeSwap` — inspect response, cancel swap, change target dynamically
- `htmx:afterSwap` — initialize third-party libraries on new content
- `htmx:load` — fires when new content is loaded (use `htmx.onLoad(callback)` helper)
- `htmx:beforeHistorySave` — clean up third-party DOM mutations before snapshot
- Client-side events triggered via `HX-Trigger` response header fire as native DOM events
### Key Extensions
- **idiomorph** — morph swap strategy for DOM preservation (focus, video, scroll position)
- **response-targets** — swap different elements based on HTTP status codes (`hx-target-404`, `hx-target-422`)
- **head-support** — merge `<head>` changes (title, meta, link) from HTMX responses
- **preload** — prefetch content on hover/mousedown for instant navigation
- **ws** — WebSocket support via `hx-ext="ws"` and `ws-connect="/ws"`
- **sse** — Server-Sent Events via `hx-ext="sse"` and `sse-connect="/sse"`
- Enable extensions on parent element: `hx-ext="response-targets, preload"`
Installation
Create htmx.mdc in your project's .cursor/rules/ directory and paste the configuration above. Cursor and Windsurf both read .cursor/rules/ — Copilot users place it in .github/copilot-instructions.md instead.
<!-- Install via CDN -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js"
integrity="sha384-H5SrcfygHmAuTDZphMHqBJLc3FhssKjG7w/CeCpFReSfwBWDTKpkzPP8c+cLsK+V"
crossorigin="anonymous"></script>
<!-- Or install via npm -->
npm install [email protected]
Examples
<!-- Search-as-you-type with debounce -->
<input
type="text"
name="q"
placeholder="Search..."
hx-get="/search"
hx-trigger="keyup changed delay:500ms"
hx-target="#results"
hx-indicator="#spinner" />
<div id="results" role="region" aria-live="polite"></div>
<img id="spinner" class="htmx-indicator" src="/spinner.svg" />
<!-- Inline form with out-of-band toast -->
<form hx-post="/items" hx-target="this" hx-swap="outerHTML">
<input name="name" required />
<button type="submit">Add Item</button>
</form>
<!-- Server response: -->
<!-- <form hx-post="/items" ...> (reset form) -->
<!-- <div id="toast" hx-swap-oob="true">Item added successfully!</div> -->
<!-- Infinite scroll with intersection trigger -->
<div
hx-get="/posts?page=2"
hx-trigger="intersect once"
hx-swap="afterend"
hx-target="this">
<!-- Existing posts -->
</div>
<!-- Lazy-loading tabs -->
<div hx-get="/tab-1" hx-trigger="load" hx-swap="innerHTML">
Loading...
</div>
<div hx-get="/tab-2" hx-trigger="revealed" hx-swap="innerHTML">
Loading...
</div>
Related Resources
Related Articles
Bun Cursor Rules: All-in-One JavaScript Toolkit
Cursor rules for Bun covering the runtime, test runner, package manager, bundler, Bun.serve HTTP server, built-in SQLite, S3 client, HTMLRewriter, and native TypeScript/JSX support.
Django Cursor Rules: Python Web Framework Guide
Cursor rules for Django development covering models, views, serializers, ORM optimization, REST APIs, and Django REST Framework for production web applications.
Backend Framework Rules
Cursor rules for backend frameworks and runtimes — Express, Django, FastAPI, Flask, Laravel, NestJS, Rails, Spring Boot, Deno, and Bun covering middleware, ORM patterns, API design, authentication, and deployment.