# Day 3: htmx Request, Trigger, Target, and Swap Fundamentals

**Onboarding track:** Alpine.js + htmx, co-equal architecture  
**Verified baseline:** Alpine.js 3.15.12 and htmx 2.0.10 (stable)  
**Verification date:** 2026-07-19  
**Compatibility boundary:** htmx 4.0.0-beta5 is a prerelease line and is not the syntax baseline for these lessons.

## Purpose

Learn the ordinary htmx operations a developer performs every day: making requests, choosing triggers, declaring a small target, matching the response root to the swap, and preserving a valid non-enhanced path.

## How to use this document

Budget a working day. Read the lessons in order, type the examples instead of copying them blindly, perform every mini-task, and finish the exit exercise without looking at the answer notes. The examples use a fictional server-rendered operations application, but the ownership rules are framework-neutral.

A successful day ends with three artifacts:

1. working markup or a small runnable page;
2. written ownership notes for each state value and DOM region;
3. answers to the comprehension check.

## Learning outcomes

You will be able to:

- use `hx-get`, `hx-post`, `hx-put`, `hx-patch`, and `hx-delete` with appropriate server semantics;
- select triggers, targets, and swap strategies intentionally;
- return HTML fragments whose root matches the target contract;
- keep server authority visible;
- avoid accidental client rendering and broad destructive swaps.

## 1. The htmx mental model

htmx lets an element describe an HTTP interaction in HTML.

```html
<button
    type="button"
    hx-get="/customers/42/card"
    hx-target="#customer-panel"
    hx-swap="innerHTML"
>
    Load customer
</button>

<section id="customer-panel"></section>
```

Read it as:

1. `hx-get` chooses the request.
2. The button’s default trigger is `click`.
3. `hx-target` declares the server-owned replacement boundary.
4. `hx-swap` declares how response HTML enters the target.

The server normally returns HTML:

```html
<article>
    <h2>Ada Lovelace</h2>
    <p>Customer since 2024</p>
</article>
```

htmx is not a JSON-to-component rendering loop. URLs, forms, HTTP, server templates, and HTML remain central.

## 2. Request-method SOP

### GET: retrieve a representation

```html
<a
    href="/orders?page=2"
    hx-get="/orders?page=2"
    hx-target="#orders"
    hx-push-url="true"
>
    Next page
</a>
```

GET should not make a durable mutation. A real `href` keeps navigation meaningful.

### POST: create or submit a command

```html
<form
    action="/customers"
    method="post"
    hx-post="/customers"
    hx-target="#customer-form"
    hx-swap="outerHTML"
>
    <!-- named controls -->
</form>
```

### PUT/PATCH: update according to server conventions

HTML forms natively submit GET or POST. `hx-put` and `hx-patch` are useful enhanced interactions, but the server must authorize and validate them. Define the non-enhanced fallback according to the project’s routing conventions.

### DELETE: delete an authorized resource

```html
<button
    type="button"
    hx-delete="/notifications/42"
    hx-target="closest li"
    hx-swap="delete"
>
    Dismiss
</button>
```

Return a successful response appropriate to the project. Under htmx 2.x, a `204` causes no swap; when relying on a `delete` swap, use a tested successful response policy such as `200`.

## 3. Targets are lifecycle contracts

A target is not merely a selector convenience.

Before choosing one, ask:

- Does it contain an Alpine root?
- Should that local state survive?
- Does it contain focused input?
- Does it contain a third-party widget?
- Is it larger than the returned representation?
- Does the response contain the correct root for the swap?
- Is the target stable under repeated requests?
- Could an out-of-band response also address it by ID?

Target the smallest coherent server-owned component.

### Good

```html
<section x-data="{ open: true }">
    <div id="report-results">
        <!-- htmx-owned -->
    </div>
</section>
```

`#report-results` can be replaced without destroying the stable Alpine shell.

### Suspicious

```html
<main id="app" x-data="{ sidebarOpen: true, modalOpen: false }">
    ...
</main>

<button hx-get="/report" hx-target="#app">Refresh report</button>
```

A report refresh should not destroy unrelated shell state.

## 4. Swap strategy SOP

| Strategy | Meaning | Typical use |
| --- | --- | --- |
| `innerHTML` | keep target root; replace children | stable server panel shell |
| `outerHTML` | replace target root | complete server component/form/row |
| `beforeend` | append response | load more, feed page |
| `afterbegin` | prepend response | newest item at top |
| `delete` | remove target after successful request | authorized deletion |
| `none` | no primary swap | response events or OOB-only response |

### `innerHTML`

```html
<section id="customer-panel"></section>
```

Response:

```html
<article>...</article>
```

### `outerHTML`

```html
<article id="customer-42">...</article>
```

Response must include:

```html
<article id="customer-42">...</article>
```

A common failure is returning only the article’s children while requesting `outerHTML`.

### Append

```html
<ul id="activity-feed">
    <!-- existing rows -->
</ul>

<button
    hx-get="/activity?page=2"
    hx-target="#activity-feed"
    hx-swap="beforeend"
>
    Load more
</button>
```

The response should contain list items, not a second nested `<ul>` unless that is intentional.

## 5. Triggers

Default triggers depend on the element: click for many controls, submit for forms, change for some controls. Use `hx-trigger` when timing differs.

### Debounced input

```html
<input
    id="customer-query"
    type="search"
    name="q"
    hx-get="/customers/search"
    hx-trigger="input changed delay:300ms"
    hx-target="#customer-results"
    hx-sync="this:replace"
>
```

`changed` means the element’s value must differ. `delay` debounces. `hx-sync` handles an already in-flight request; Day 5 covers it in depth.

### Polling

```html
<div
    hx-get="/jobs/42/status"
    hx-trigger="every 2s"
    hx-target="this"
    hx-swap="innerHTML"
>
    Waiting for status…
</div>
```

The server remains authoritative for job state.

### Custom events

```html
<section
    hx-get="/report"
    hx-trigger="report-refresh-requested from:window"
    hx-target="this"
    hx-swap="outerHTML"
>
    Current report
</section>
```

Use semantic custom events. Day 7 defines the event contract.

### Scope caution

A `from:` selector is evaluated according to htmx trigger rules and may be broader than the visual component. Prefer explicit IDs, `window` for deliberate cross-component events, or attributes placed directly on the triggering control. Do not write `from:input` on a large page and assume it means “inputs inside this form.”

## 6. Parameters and native controls

Forms and named controls are the normal request model.

```html
<form
    action="/search"
    method="get"
    hx-get="/search"
    hx-target="#results"
>
    <input type="search" name="q">
    <select name="status">
        <option value="">All</option>
        <option value="open">Open</option>
    </select>
    <button type="submit">Search</button>
</form>
```

Additional tools, in preferred order:

1. real named controls;
2. `hx-include` for related controls outside the triggering element;
3. `hx-params` to include or exclude parameters;
4. `hx-vals` for small explicit supplemental values;
5. `hx-headers` for deliberate metadata such as a framework-approved CSRF header.

Avoid serializing a large Alpine store into `hx-vals`.

## 7. Response design

After a mutation, return the resulting authoritative representation rather than a bare success object.

Request:

```html
<form
    hx-post="/profile"
    hx-target="#profile-form"
    hx-swap="outerHTML"
>
```

Success response:

```html
<section id="profile-form">
    <p role="status">Profile saved.</p>
    <!-- canonical profile representation -->
</section>
```

Validation response:

```html
<form
    id="profile-form"
    action="/profile"
    method="post"
    hx-post="/profile"
    hx-target="#profile-form"
    hx-swap="outerHTML"
>
    <div role="alert">
        Correct the highlighted fields.
    </div>
    <!-- submitted values and field errors -->
</form>
```

Under htmx 2.x, `4xx` and `5xx` responses do not swap by default. A team must choose one tested policy:

- return validation HTML with a swap-eligible status such as `200`; or
- configure error-response swapping deliberately.

Do not depend on accidental defaults.

## 8. Progressive enhancement baseline

Prefer:

- real links with `href`;
- forms with `action` and `method`;
- meaningful routes;
- complete pages for direct visits;
- server-rendered errors;
- buttons only when there is no navigation fallback.

A route used with `hx-push-url` must return a complete page when opened directly.

If the same URL returns a full page for normal navigation and a fragment for htmx, configure HTTP caches correctly, commonly with `Vary: HX-Request` or an equivalent framework policy.

## 9. Request headers and server behavior

htmx sends request metadata such as `HX-Request` and `HX-Target`. These are hints for representation selection, not authorization credentials.

The server must still:

- authenticate;
- authorize every operation;
- validate all input;
- enforce CSRF policy;
- perform transactions;
- handle conflicts;
- escape untrusted output;
- return coherent HTML.

## 10. Day lab: customer directory

Build:

1. a real search GET form;
2. a paginated result region;
3. a load-customer detail panel;
4. an inline delete action for a notification;
5. an edit form that replaces its complete root.

For each interaction, write:

```text
Method:
Route:
Trigger:
Inputs:
Target:
Swap:
Expected response root:
Non-enhanced behavior:
Status-code policy:
```

Test each route directly without htmx.

## Mini-tasks

### Task A: match roots

Repair three `outerHTML` examples whose server response omits the target root.

### Task B: shrink targets

A refresh button targets the entire dashboard. Refactor it to target one coherent report card.

### Task C: replace JSON rendering

Convert an Alpine `fetch('/orders').then(r => r.json())` list renderer into an htmx request returning server HTML.

### Task D: trigger scope

Replace a broad `hx-trigger="input from:input"` with a component-safe trigger arrangement.

## Comprehension check

1. What four decisions are expressed by a basic htmx interaction?
2. Why is an `hx-target` a lifecycle contract?
3. When should `outerHTML` be used?
4. What must its response contain?
5. What is the difference between `delay` and `hx-sync`?
6. Why should GET not make durable changes?
7. What happens with a `204` response in htmx 2.x?
8. Why are real links and forms valuable even in an enhanced application?
9. When is `hx-include` preferable to `hx-vals`?
10. Why is `HX-Request` not an authorization mechanism?

## Cheat sheet

```text
hx-get/post/...  request
hx-trigger       when request starts
hx-target        server-owned replacement boundary
hx-swap          how response enters
hx-include       include related controls
hx-push-url      create navigable history state
hx-sync          coordinate overlapping requests
hx-indicator     request progress
hx-swap-oob      related authoritative update elsewhere
```

## Exit exercise

Implement a server-rendered “support tickets” page with search, pagination, details, and status update. Every pushed URL must work directly. No server list may be rebuilt with `x-for`. Alpine may be used only for a local filter disclosure and must survive result swaps.

## Exit criteria

You pass when every htmx interaction has:

- a meaningful route and method;
- declared inputs;
- the smallest coherent target;
- a response root that matches the swap;
- an explicit success/error policy;
- a valid direct or non-enhanced path where practical.

## Official reference shelf

Use these as the authority when a project version or behavior is uncertain:

- Alpine.js documentation: https://alpinejs.dev/
- Alpine.js directives: https://alpinejs.dev/directives/
- Alpine.js `Alpine.data()` lifecycle and `destroy()`: https://alpinejs.dev/globals/alpine-data
- htmx 2.x documentation: https://htmx.org/docs/
- htmx 2.x reference: https://htmx.org/reference/
- htmx 2.x events: https://htmx.org/events/
- htmx 4 migration guide: https://four.htmx.org/migration-guide-htmx-4/

> Version note: These exercises use htmx 2.x event names such as `htmx:afterSwap` and `htmx:afterSettle`. htmx 4 changes event naming and several defaults. Do not mechanically port the examples without the migration guide.
