# HTMX Attributes Reference

Complete reference for all HTMX attributes with Spring Boot examples.

## Core Attributes

### hx-get

Issues GET request to specified URL.

```html
<button hx-get="/users" hx-target="#results">Load Users</button>

<!-- With Thymeleaf -->
<button hx:get="@{/users/{id}(id=${user.id})}" hx-target="#details">
  View Details
</button>
```

### hx-post

Issues POST request to specified URL.

```html
<form hx-post="/users" hx-target="#user-list" hx-swap="beforeend">
  <input name="name" required />
  <button type="submit">Create</button>
</form>

<!-- With Thymeleaf -->
<form hx:post="@{/items}" hx-target="#items">
  <input th:field="*{name}" />
  <button type="submit">Add Item</button>
</form>
```

### hx-put

Issues PUT request for updates.

```html
<form hx-put="/users/123" hx-target="#user-card">
  <input name="name" value="John Doe" />
  <button type="submit">Update</button>
</form>

<!-- With Thymeleaf -->
<form hx:put="@{/users/{id}(id=${user.id})}" th:object="${user}">
  <input th:field="*{name}" />
  <button type="submit">Save</button>
</form>
```

### hx-delete

Issues DELETE request.

```html
<button
  hx-delete="/users/123"
  hx-target="closest tr"
  hx-swap="outerHTML swap:1s"
  hx-confirm="Delete this user?"
>
  Delete
</button>

<!-- With Thymeleaf -->
<button
  hx:delete="@{/items/{id}(id=${item.id})}"
  hx-target="closest tr"
  th:attr="hx-confirm='Delete ' + ${item.name} + '?'"
>
  Delete
</button>
```

### hx-patch

Issues PATCH request for partial updates.

```html
<button
  hx-patch="/users/123"
  hx-vals='{"status": "active"}'
  hx-target="#user-status"
>
  Activate
</button>
```

---

## Target and Swap

### hx-target

Specifies element to swap response into.

```html
<!-- ID selector -->
<button hx-get="/data" hx-target="#results">Load</button>

<!-- CSS selector -->
<button hx-get="/notification" hx-target=".notification-area">Notify</button>

<!-- Closest parent -->
<button hx-delete="/items/123" hx-target="closest tr">Delete</button>

<!-- Find in parent -->
<button hx-get="/status" hx-target="closest .card > .status">Status</button>

<!-- this (replace button itself) -->
<button hx-get="/edit-form" hx-target="this">Edit</button>

<!-- next sibling -->
<button hx-get="/content" hx-target="next .content-area">Load</button>

<!-- previous sibling -->
<button hx-get="/content" hx-target="previous .section">Load Previous</button>
```

### hx-swap

Controls how response is swapped.

```html
<!-- innerHTML (default) -->
<div hx-get="/content" hx-swap="innerHTML">Content</div>

<!-- outerHTML (replace element itself) -->
<div hx-get="/content" hx-swap="outerHTML">Replace Me</div>

<!-- beforebegin (before element) -->
<div hx-get="/content" hx-swap="beforebegin">Insert Before</div>

<!-- afterbegin (first child) -->
<div hx-get="/content" hx-swap="afterbegin">Prepend</div>

<!-- beforeend (last child) -->
<div hx-get="/content" hx-swap="beforeend">Append</div>

<!-- afterend (after element) -->
<div hx-get="/content" hx-swap="afterend">Insert After</div>

<!-- delete (remove element) -->
<div hx-get="/content" hx-swap="delete">Remove</div>

<!-- none (don't swap, only fire events) -->
<button hx-post="/track" hx-swap="none">Track Click</button>
```

### Swap Modifiers

```html
<!-- Scroll to top after swap -->
<div hx-get="/content" hx-swap="innerHTML scroll:top">Content</div>

<!-- Scroll to bottom -->
<div hx-get="/messages" hx-swap="beforeend scroll:bottom">Messages</div>

<!-- Show element after swap -->
<div hx-get="/content" hx-swap="innerHTML show:true">Content</div>

<!-- Swap with delay -->
<div hx-get="/content" hx-swap="innerHTML swap:1s">Fade In</div>

<!-- Settle with delay -->
<div hx-get="/content" hx-swap="innerHTML settle:2s">Settle Slowly</div>

<!-- Ignore title -->
<div hx-get="/content" hx-swap="innerHTML ignoreTitle:true">Content</div>

<!-- Combined modifiers -->
<div
  hx-get="/content"
  hx-swap="innerHTML swap:0.5s settle:1s scroll:top show:true"
>
  Content
</div>
```

### hx-swap-oob

Out-of-band swap (update multiple elements).

```html
<!-- In response HTML -->
<div id="main-content">Main Content</div>

<div id="sidebar" hx-swap-oob="true">Updated Sidebar</div>

<div id="notification" hx-swap-oob="innerHTML">New notification!</div>

<div id="counter" hx-swap-oob="outerHTML">
  <span id="counter">Count: 42</span>
</div>
```

---

## Triggers

### hx-trigger

Specifies event that triggers request.

```html
<!-- Default (click for buttons, submit for forms) -->
<button hx-get="/data">Click Me</button>

<!-- Explicit click -->
<div hx-get="/data" hx-trigger="click">Click Here</div>

<!-- Multiple events -->
<input hx-get="/search" hx-trigger="keyup, change" hx-target="#results" />

<!-- Event filters -->
<input
  hx-get="/search"
  hx-trigger="keyup[keyCode==13]"
  hx-target="#results"
  placeholder="Press Enter"
/>

<!-- Event modifiers -->
<div hx-get="/data" hx-trigger="click once">Click Once</div>

<div hx-get="/data" hx-trigger="click changed">Click if Changed</div>

<div hx-get="/data" hx-trigger="click delay:1s">Delayed Click</div>

<div hx-get="/data" hx-trigger="click throttle:1s">Throttled</div>

<!-- Load event -->
<div hx-get="/initial-data" hx-trigger="load">Auto-Load</div>

<!-- Revealed (when element scrolls into view) -->
<div hx-get="/more-items" hx-trigger="revealed">Infinite Scroll</div>

<!-- Intersect (custom intersection) -->
<div hx-get="/data" hx-trigger="intersect once threshold:0.5">
  Load When Half Visible
</div>

<!-- Every (polling) -->
<div hx-get="/status" hx-trigger="every 5s">Auto-Refresh</div>

<!-- Custom event -->
<div hx-get="/data" hx-trigger="customEvent">Listen for Custom</div>

<!-- From another element -->
<div hx-get="/data" hx-trigger="click from:#other-button">
  Triggered Remotely
</div>
<button id="other-button">Click Me</button>

<!-- Queue options -->
<div hx-get="/data" hx-trigger="click queue:first">Queue First</div>
<div hx-get="/data" hx-trigger="click queue:last">Queue Last</div>
<div hx-get="/data" hx-trigger="click queue:all">Queue All</div>
<div hx-get="/data" hx-trigger="click queue:none">No Queue</div>
```

### Common Trigger Patterns

```html
<!-- Search with debounce -->
<input
  type="search"
  hx-get="/search"
  hx-trigger="keyup changed delay:500ms, search"
  hx-target="#results"
/>

<!-- Form validation on blur -->
<input
  name="email"
  hx-post="/validate/email"
  hx-trigger="blur"
  hx-target="next .validation-msg"
/>

<!-- Auto-save on change -->
<textarea
  hx-post="/save"
  hx-trigger="keyup changed delay:2s"
  hx-target="#save-status"
></textarea>

<!-- Load more on scroll -->
<div
  hx-get="/items?page=2"
  hx-trigger="revealed"
  hx-target="#items-list"
  hx-swap="beforeend"
>
  Loading...
</div>

<!-- Conditional refresh -->
<div
  hx-get="/status"
  hx-trigger="every 10s[document.visibilityState == 'visible']"
>
  Status
</div>
```

---

## Request Configuration

### hx-vals

Add values to request.

```html
<!-- Static JSON -->
<button hx-post="/search" hx-vals='{"category": "books"}'>Search Books</button>

<!-- JavaScript expression -->
<button hx-post="/data" hx-vals="js:{timestamp: Date.now()}">Submit</button>

<!-- Thymeleaf dynamic -->
<form hx-post="/search" hx:vals="${{query: searchTerm, userId: user.id}}">
  <button type="submit">Search</button>
</form>
```

### hx-headers

Add custom headers to request.

```html
<!-- Static headers -->
<div hx-get="/api/data" hx-headers='{"X-Custom-Header": "value"}'>Load</div>

<!-- JavaScript expression -->
<div hx-get="/api/data" hx-headers="js:{'X-Request-ID': generateRequestId()}">
  Load
</div>

<!-- Thymeleaf dynamic -->
<div
  hx-get="/api/data"
  hx:headers="${{'X-User-Id': user.id, 'X-Session': session.id}}"
>
  Load
</div>
```

### hx-params

Filter parameters sent with request.

```html
<!-- Include all -->
<form hx-post="/users" hx-params="*">All Params</form>

<!-- Include none -->
<form hx-post="/users" hx-params="none">No Params</form>

<!-- Include specific -->
<form hx-post="/users" hx-params="name,email">Only Name & Email</form>

<!-- Exclude specific -->
<form hx-post="/users" hx-params="*,!password">All Except Password</form>
```

### hx-include

Include values from other elements.

```html
<!-- Include by selector -->
<button hx-post="/submit" hx-include="[name='csrf']">Submit</button>
<input type="hidden" name="csrf" value="token123" />

<!-- Include multiple -->
<button hx-post="/filter" hx-include=".filter-input">Apply Filters</button>

<!-- Include closest form -->
<button hx-post="/save" hx-include="closest form">Save</button>
```

---

## Response Headers

HTMX responds to these server-sent headers:

### HX-Trigger

Trigger client-side event after response.

```java
// Server-side (Spring Boot)
@HxRequest
@HxTrigger("user-updated")
@PostMapping("/users/{id}")
public String updateUser(@PathVariable Long id) {
    // Update logic
    return "fragments/user-card";
}

// Or programmatically
return HtmxResponse.builder()
    .view("fragments/user-card")
    .trigger("user-updated")
    .build();
```

```html
<!-- Client-side listener -->
<script>
  document.body.addEventListener("user-updated", function (evt) {
    console.log("User was updated!");
  });
</script>
```

### HX-Trigger with Data

```java
// Server sends JSON data with event
String trigger = "userUpdated:{\"id\":" + user.getId() +
                ",\"name\":\"" + user.getName() + "\"}";
return HtmxResponse.builder()
    .trigger(trigger)
    .view("fragments/user-card")
    .build();
```

```html
<script>
  document.body.addEventListener("userUpdated", function (evt) {
    const data = evt.detail.value; // {id: 123, name: "John"}
    console.log("User updated:", data);
  });
</script>
```

### HX-Retarget

Change target element for swap.

```java
@HxRequest
@HxRetarget("#notification-area")
@PostMapping("/action")
public String action() {
    return "fragments/notification";
}
```

### HX-Reswap

Change swap strategy.

```java
return HtmxResponse.builder()
    .reswap(HtmxReswap.beforeend())
    .view("fragments/item")
    .build();
```

### HX-Push-Url

Push URL to browser history.

```java
return HtmxResponse.builder()
    .pushUrl("/users/" + user.getId())
    .view("fragments/user-details")
    .build();
```

### HX-Redirect

Client-side redirect (full page reload).

```java
return HtmxResponse.builder()
    .redirect("/dashboard")
    .build();
```

### HX-Refresh

Refresh entire page.

```java
return HtmxResponse.builder()
    .refresh()
    .build();
```

---

## Indicators and Loading

### hx-indicator

Show loading indicator.

```html
<button hx-get="/slow-data" hx-target="#data" hx-indicator="#loading-spinner">
  Load Data
</button>

<div id="loading-spinner" class="htmx-indicator">
  <img src="/spinner.gif" /> Loading...
</div>
```

**CSS:**

```css
.htmx-indicator {
  display: none;
}

.htmx-request .htmx-indicator {
  display: inline;
}

.htmx-request.htmx-indicator {
  display: inline;
}
```

### Custom Loading States

```html
<style>
  .loading-state {
    opacity: 0.5;
    pointer-events: none;
  }
</style>

<div hx-get="/data" hx-indicator=".loading-overlay" class="data-container">
  Data Content
</div>

<div class="loading-overlay htmx-indicator">
  <div class="spinner"></div>
  Loading...
</div>
```

---

## Synchronization

### hx-sync

Synchronize requests.

```html
<!-- Drop concurrent requests -->
<form hx-post="/save" hx-sync="this:drop">
  <input name="data" />
  <button type="submit">Save</button>
</form>

<!-- Abort in-flight request -->
<form hx-post="/search" hx-sync="this:abort">
  <input
    name="q"
    hx-get="/search"
    hx-trigger="keyup changed delay:500ms"
    hx-target="#results"
  />
</form>

<!-- Replace in-flight request -->
<button hx-get="/data" hx-sync="this:replace">Load</button>

<!-- Queue requests -->
<button hx-get="/data" hx-sync="this:queue">Queue</button>

<!-- Sync with another element -->
<div hx-get="/data" hx-sync="closest form:abort">Load</div>
```

---

## Validation

### hx-validate

Force validation before submit.

```html
<form hx-post="/users">
  <input name="email" type="email" required />
  <button type="submit" hx-validate="true">Submit</button>
</form>
```

---

## Extensions

### hx-ext

Enable HTMX extensions.

```html
<!-- JSON encoding -->
<form hx-post="/api/users" hx-ext="json-enc">
  <input name="name" value="John" />
  <button type="submit">Submit as JSON</button>
</form>

<!-- Multiple extensions -->
<div hx-get="/data" hx-ext="json-enc, debug">Load</div>

<!-- Extension configuration -->
<div
  hx-get="/data"
  hx-ext="loading-states"
  loading-states='{"show": ".spinner"}'
>
  Content
</div>
```

---

## Boosting

### hx-boost

Convert regular links/forms to HTMX.

```html
<!-- Boost entire section -->
<div hx-boost="true">
  <a href="/page1">Page 1</a>
  <a href="/page2">Page 2</a>
  <form action="/search" method="get">
    <input name="q" />
    <button type="submit">Search</button>
  </form>
</div>

<!-- Disable boost on specific element -->
<div hx-boost="true">
  <a href="/page1">Boosted</a>
  <a href="/external" hx-boost="false">Not Boosted</a>
</div>
```

---

## History

### hx-push-url

Push URL to history.

```html
<!-- Push current URL -->
<a href="/page1" hx-get="/page1" hx-push-url="true">Page 1</a>

<!-- Push custom URL -->
<button hx-get="/data" hx-push-url="/custom-url">Load</button>

<!-- Don't push -->
<button hx-get="/data" hx-push-url="false">Load (No History)</button>
```

### hx-replace-url

Replace URL without adding history.

```html
<button hx-get="/step2" hx-replace-url="true">Next Step</button>
```

### hx-history-elt

Snapshot element for history.

```html
<div hx-history-elt="#content">
  <div id="content">
    <!-- This element state is saved in history -->
  </div>
</div>
```

---

## Miscellaneous

### hx-confirm

Show confirmation dialog.

```html
<button
  hx-delete="/users/123"
  hx-confirm="Are you sure you want to delete this user?"
>
  Delete
</button>

<!-- With Thymeleaf -->
<button
  hx:delete="@{/items/{id}(id=${item.id})}"
  th:attr="hx-confirm='Delete ' + ${item.name} + '?'"
>
  Delete
</button>
```

### hx-disable

Disable element during request.

```html
<button hx-post="/save" hx-disable>Save</button>
```

### hx-disabled-elt

Disable other elements during request.

```html
<button hx-post="/save" hx-disabled-elt="#submit-btn, #cancel-btn">Save</button>
```

### hx-encoding

Set request encoding.

```html
<!-- Multipart form data (file uploads) -->
<form hx-post="/upload" hx-encoding="multipart/form-data">
  <input type="file" name="file" />
  <button type="submit">Upload</button>
</form>
```

### hx-preserve

Preserve element during swap.

```html
<div hx-get="/content" hx-target="#container">
  <div id="container">
    <input type="text" hx-preserve value="Keep this value" />
  </div>
</div>
```

### hx-prompt

Prompt user for input.

```html
<button hx-delete="/users/123" hx-prompt="Enter 'DELETE' to confirm">
  Delete
</button>
```

### hx-select

Select subset of response.

```html
<!-- Only swap matching content from response -->
<div hx-get="/page" hx-select="#content" hx-target="#main">
  Load Page Content Only
</div>
```

### hx-select-oob

Select OOB content from response.

```html
<div
  hx-get="/page"
  hx-select="#main-content"
  hx-select-oob="#sidebar, #notifications"
  hx-target="#content"
>
  Load with OOB
</div>
```

---

## Events

HTMX triggers these events:

```html
<script>
  // Before request sent
  document.body.addEventListener("htmx:beforeRequest", function (evt) {
    console.log("Sending request to:", evt.detail.xhr.url);
  });

  // After response received
  document.body.addEventListener("htmx:afterRequest", function (evt) {
    console.log("Response status:", evt.detail.xhr.status);
  });

  // After swap
  document.body.addEventListener("htmx:afterSwap", function (evt) {
    console.log("Content swapped");
  });

  // After settle
  document.body.addEventListener("htmx:afterSettle", function (evt) {
    console.log("Content settled");
  });

  // Request error
  document.body.addEventListener("htmx:responseError", function (evt) {
    console.error("Request failed:", evt.detail.error);
  });

  // Validation error
  document.body.addEventListener("htmx:validation:failed", function (evt) {
    console.error("Validation failed:", evt.detail.errors);
  });
</script>
```

---

## Best Practices

1. **Use hx-target explicitly** - Don't rely on defaults
2. **Combine triggers wisely** - `keyup changed delay:500ms` for search
3. **Always handle errors** - Use global error event listeners
4. **Show loading states** - Use `hx-indicator` for better UX
5. **Use hx-sync to prevent race conditions** - Especially for forms
6. **Leverage hx-swap modifiers** - Add animations and scroll behavior
7. **Use hx-confirm for destructive actions** - Prevent accidental deletions
8. **Enable hx-boost for SPA feel** - Convert site to AJAX navigation
9. **Use OOB swaps efficiently** - Update multiple elements in one request
10. **Test with slow connections** - Ensure loading states work properly
