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.

June 10, 2026by PromptGenius Team
htmxcursor-rulesfrontendhypermediaajaxprogressive-enhancement
HTMX Cursor Rules: Hypermedia-Driven Frontend

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>