# htmx for Developers Who Already Know Alpine.js

## Purpose

This guide teaches htmx from an Alpine-shaped starting point.

> **Version baseline:** The examples target Alpine.js 3.x and stable htmx 2.x. htmx 4 is currently a prerelease line with changed event names, inheritance defaults, and error-swapping behavior; verify its migration guide before applying these examples to htmx 4.

You already know how to:

- place local behavior in HTML;
- create reactive component state with `x-data`;
- bind controls with `x-model`;
- react to browser events;
- show, hide, repeat, and transition local DOM.

htmx adds a different capability: **elements can make HTTP requests for server-rendered HTML and install the result into a declared DOM region**.

The useful division is:

| Need | Default owner |
| --- | --- |
| Immediate browser-local interaction | Alpine |
| Durable or shared truth | Server |
| Request server-rendered HTML | htmx |
| Choose when a request fires | htmx |
| Choose which region receives the response | htmx |
| Maintain bookmarkable navigation state | URL plus htmx history support |
| Model a submitted control locally | Alpine plus native HTML |
| Display ordinary request progress | htmx lifecycle plus CSS |

The key shift is this:

> Do not fetch data merely so Alpine can rebuild markup the server already knows how to render.

---

## 1. The htmx mental model

htmx extends HTML attributes so an element can describe a server interaction.

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

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

Read this as:

1. `hx-get` chooses the request.
2. the click is the default trigger for this button;
3. `hx-target` declares the replacement boundary;
4. `hx-swap` describes how returned HTML enters that target.

The server returns HTML, not necessarily JSON.

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

htmx installs that representation directly.

This is not a client-side rendering loop. It is enhanced hypermedia: URLs, forms, HTTP, server templates, and HTML remain central.

---

## 2. Installation

A typical page can load htmx and Alpine together.

```html
<script src="/vendor/htmx.min.js" defer></script>
<script src="/vendor/alpine.min.js" defer></script>
```

Use pinned or self-hosted assets according to project policy.

No build step is required for ordinary attribute-driven usage. Your server must still provide routes, authorize requests, validate input, and render complete pages or fragments.

---

## 3. Translating Alpine instincts into htmx instincts

### Alpine asks “how should this DOM react to local state?”

htmx asks “what server representation should replace this region?”

```html
<!-- Alpine: local visibility -->
<div x-data="{ open: false }">
    <button type="button" @click="open = true">Open</button>
    <section x-show="open">Already present content</section>
</div>

<!-- htmx: remote representation -->
<button
    hx-get="/reports/quarterly"
    hx-target="#report"
>
    Load quarterly report
</button>
<section id="report"></section>
```

### `x-data` defines local reactive ownership

`hx-target` defines server replacement ownership.

Do not casually target a large ancestor that contains Alpine state expected to survive. Either target a smaller inner region or intentionally replace the Alpine root so its state resets.

### Alpine methods are not the default network layer

Avoid this as the ordinary pattern when the desired result is server HTML:

```html
<div x-data="{
    async load() {
        const response = await fetch('/customers')
        this.customers = await response.json()
    }
}">
```

Prefer:

```html
<button
    hx-get="/customers"
    hx-target="#customers"
>
    Load customers
</button>

<section id="customers"></section>
```

Use `fetch()` for genuinely data-oriented or specialized operations, not as a reflex whenever Alpine can run an async function.

---

## 4. Request attributes

### `hx-get`

Use GET to retrieve a representation without changing authoritative state.

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

The real `href` preserves ordinary navigation. htmx enhances the interaction and updates the result region.

### `hx-post`, `hx-put`, `hx-patch`, and `hx-delete`

Use mutation methods for durable changes.

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

After a mutation, prefer returning the resulting authoritative component rather than a bare success object.

```html
<section id="profile-form">
    <p>Profile saved.</p>
    <!-- authoritative current profile representation -->
</section>
```

The server still owns:

- authorization;
- validation;
- transactions;
- identity;
- business rules;
- the canonical result.

htmx transports the request and installs the representation.

---

## 5. Triggers

`hx-trigger` defines when a request begins.

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

Typical triggers include:

- clicks;
- form submissions;
- changed controls;
- debounced input;
- revealed or intersecting elements;
- polling;
- custom browser events.

Keep request timing with htmx when it concerns network behavior. Alpine may decide that a semantic action occurred, but htmx should still own debounce, replacement, cancellation, and queue policy.

A custom event from Alpine can trigger a request.

```html
<div x-data>
    <button
        type="button"
        @click="$dispatch('filters-committed')"
    >
        Apply filters
    </button>
</div>

<form
    hx-get="/search"
    hx-trigger="filters-committed from:window"
    hx-target="#results"
>
    <!-- real named controls -->
</form>
```

---

## 6. Targets and swaps

### `hx-target`: declare the server-owned boundary

```html
<button
    hx-get="/account/security"
    hx-target="#settings-panel"
>
    Security
</button>

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

A target is not merely a selector convenience. It is a lifecycle contract.

Before choosing a target, ask:

- Does it contain an Alpine root?
- Should that Alpine state survive?
- Does it contain focused input?
- Does it contain a third-party widget?
- Is the region larger than the server result requires?
- Does the response return the correct root element for the swap?

Target the smallest coherent server-owned component.

### `hx-swap`: choose how the response enters

Common strategies:

| Strategy | Use |
| --- | --- |
| `innerHTML` | Keep the target shell; replace its contents |
| `outerHTML` | Replace the complete component root |
| `beforeend` | Append a page or feed item |
| `afterbegin` | Prepend newly arrived content |
| `delete` | Remove the target after authoritative deletion |
| `none` | Do not perform a primary swap; events or OOB updates may still matter |

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

Choose based on the semantic result, not only on what is easiest to select.

---

## 7. Forms: the natural Alpine–htmx bridge

Native controls remain the best request model.

```html
<form
    x-data="{ email: '' }"
    action="/newsletter"
    method="post"
    hx-post="/newsletter"
    hx-target="#newsletter"
    hx-swap="outerHTML"
>
    <label>
        Email
        <input
            type="email"
            name="email"
            required
            x-model="email"
        >
    </label>

    <p x-show="email.length > 0">
        A confirmation will be sent to
        <strong x-text="email"></strong>.
    </p>

    <button type="submit">Subscribe</button>
</form>
```

Ownership:

- HTML owns labels, names, and successful controls;
- Alpine owns the immediate preview;
- htmx owns submission and replacement;
- the server owns validation and persistence.

Do not make the server depend on an Alpine property that is not submitted.

### Additional request inputs

Prefer this order:

1. real named controls;
2. `hx-include` for related controls elsewhere;
3. `hx-params` to constrain parameters;
4. `hx-vals` for small explicit supplemental values;
5. `hx-headers` for deliberate request metadata.

```html
<input id="active-team" type="hidden" name="team_id" value="7">

<button
    hx-post="/invite"
    hx-include="#active-team, closest form"
    hx-target="#invite-result"
>
    Send invite
</button>
```

Avoid serializing a large Alpine store through `hx-vals` as an undocumented private application protocol.

---

## 8. Request state and concurrency

htmx already knows whether its request is in flight. Use that as the source of truth.

```html
<form
    hx-post="/profile"
    hx-target="#profile"
    hx-indicator="#profile-spinner"
    hx-disabled-elt="find button, find input"
>
    <!-- controls -->
    <button type="submit">Save</button>
    <span id="profile-spinner" class="htmx-indicator">Saving…</span>
</form>
```

Ordinary loading UI should use:

- `hx-indicator`;
- the `htmx-request` class;
- `hx-disabled-elt`;
- htmx lifecycle events;
- CSS.

Avoid a separate Alpine `loading` boolean for every request. Alpine may still own broader workflow state when “request in flight” is not the whole story.

### `hx-sync`

Use synchronization for overlapping requests.

```html
<form hx-post="/account" hx-sync="this:replace">
    <input
        name="username"
        hx-post="/account/validate-username"
        hx-trigger="change"
        hx-target="#username-error"
    >
    <div id="username-error"></div>
    <button type="submit">Save</button>
</form>
```

Useful cases include:

- active search;
- inline validation versus final submission;
- autosave;
- rapid quantity changes;
- repeated actions.

Do not build a competing request queue in Alpine unless the workflow genuinely exceeds htmx’s request coordination model.

---

## 9. History and navigation

Use URLs for state users should be able to bookmark, share, refresh, or navigate back to.

```html
<form
    action="/search"
    method="get"
    hx-get="/search"
    hx-target="#results"
    hx-push-url="true"
>
    <input type="search" name="q">
    <button type="submit">Search</button>
</form>
```

Use `hx-push-url` for a new history entry and `hx-replace-url` when the current entry should change without adding another step.

Do not put ephemeral interface details such as menu visibility in the URL. Conversely, do not use an Alpine store as a private router for meaningful server locations.

Every URL pushed by htmx should work when:

- pasted into a new tab;
- refreshed;
- opened without an htmx request;
- restored from history.

### `hx-boost`

`hx-boost` can enhance ordinary links and forms while preserving real `href` and `action` behavior.

Before boosting a large region, define policy for:

- document titles;
- focus;
- scroll;
- analytics;
- persistent Alpine shells;
- page-specific initialization;
- history restoration;
- sensitive content.

Boosting is not a substitute for valid routes and full-page responses.

---

## 10. Selecting and updating more than one region

### `hx-select`

Use `hx-select` when a response contains a larger document but the interaction needs one selected fragment.

Dedicated fragment responses are often clearer for repeated interactions, while full-page responses remain valuable for direct navigation and progressive enhancement.

### Out-of-band swaps

One server operation may legitimately update several related authoritative projections.

Example response:

```html
<section id="cart-contents">
    <!-- updated cart -->
</section>

<span id="cart-count" hx-swap-oob="outerHTML">
    3
</span>
```

Good uses:

- cart contents and cart count;
- record row and summary total;
- notifications and unread count;
- job status and completion link.

Poor uses:

- opening a drawer;
- switching a local tab;
- toggling an animation;
- updating many unrelated page regions.

Use OOB swaps for related server-authoritative consequences. Use Alpine or semantic events for local presentation reactions.

---

## 11. Server response controls and events

The server can influence htmx behavior with response headers for concerns such as:

- redirects or refreshes;
- retargeting;
- changing swap behavior;
- pushing or replacing a URL;
- emitting semantic browser events.

A powerful Alpine bridge is an htmx trigger header.

```http
HX-Trigger-After-Swap: {"record-saved":{"id":42}}
```

```html
<div
    x-data="{ open: true }"
    @record-saved.window="open = false"
>
    <!-- Alpine modal shell -->
</div>
```

The event name should describe what happened, not how Alpine must implement the response.

Use timing intentionally:

- response event immediately when DOM installation is irrelevant;
- after swap when new markup must exist;
- after settle when focus or transition work must wait.

Avoid chains where response events secretly trigger more requests, mutate global stores, and trigger further effects. A workflow should remain traceable.

---

## 12. Additional htmx tools worth knowing

### `hx-confirm` and `hx-prompt`

Use native confirmation and prompt behavior for simple request gating. When the product needs a branded or accessibility-sensitive dialog, let Alpine own the dialog and focus behavior, then dispatch a semantic event that triggers the htmx request.

### Attribute inheritance

In htmx 2.x, many attributes inherit implicitly. This can reduce repetition for targets, indicators, headers, or confirmation policy inside an obvious component boundary. Avoid large inherited zones whose behavior is invisible at the triggering element, and use explicit overrides or `hx-disinherit` where a descendant has a different contract.

### `hx-disable`

Use `hx-disable` on untrusted or intentionally inert regions so htmx does not process descendant `hx-*` attributes. This is defense in depth; server-side escaping and sanitization remain mandatory.

### File uploads

Use real file inputs and multipart encoding.

```html
<form
    hx-post="/documents"
    hx-encoding="multipart/form-data"
    hx-target="#upload-result"
>
    <input type="file" name="document" required>
    <button type="submit">Upload</button>
</form>
```

Progress comes from the request lifecycle; durable file acceptance, scanning, validation, and storage remain server responsibilities.

### Polling, SSE, and WebSockets

Core htmx polling is often enough for job status and dashboards. In stable htmx 2.x, SSE and WebSocket support are extensions rather than core attributes. Use them when the server is delivering authoritative HTML; keep local pause, selection, expansion, and presentation state in Alpine.

### Extensions generally

Add an extension only after a concrete need survives simpler options:

1. better target boundaries;
2. core htmx attributes;
3. a small plain JavaScript module;
4. an explicit extension with documented lifecycle and security effects.

---

## 13. htmx lifecycle events

htmx emits events around request construction, sending, swapping, settling, and cleanup.

Use lifecycle events for cross-cutting integration:

- authentication-expiration handling;
- error logging;
- analytics;
- request tracing;
- focus policies;
- status announcements;
- widget teardown;
- special response handling.

Do not hide application business workflows in one global `htmx:afterRequest` listener.

A simple focus example:

```html
<script>
document.body.addEventListener('htmx:afterSettle', event => {
    const autofocus = event.detail.elt.querySelector?.('[data-focus-after-swap]')
    autofocus?.focus()
})
</script>
```

Keep integration handlers narrow and documented. Alpine should own focus inside local overlays; htmx events merely tell you when server content exists.

---

## 14. Preservation, replacement, and morphing

When htmx swaps a region containing an Alpine root, that local state is normally discarded and a new component initializes from returned markup.

Choose deliberately:

### Reset

Replace the Alpine root. Correct for submitted forms, inline editors, and record rows whose temporary state should disappear.

### Survive

Keep the Alpine root stable and replace an inner target. Correct for modal shells, drawers, filter panels, and persistent local controls.

### Preserve

Use stable identity and `hx-preserve` only when moving the boundary is impractical and continued element identity is truly required.

### Reconcile

Use morphing only after ordinary replacement and better target design have failed a demonstrated requirement.

Preservation and morphing are not substitutes for deciding who owns the component.

---

## 15. Errors and validation

The server should return usable HTML for success and failure.

For validation errors, return the form with:

- submitted values;
- field messages;
- an error summary where appropriate;
- accessible relationships;
- a clear next action.

htmx installs that form. With htmx 2.x, `4xx` and `5xx` responses are not swapped by default, so return validation HTML with a swap-eligible status such as `200` or deliberately configure error-response swapping. Alpine may focus or announce errors after the swap, but it should not reconstruct the server’s validation result from an arbitrary JSON schema.

Network failures are different from application validation. Establish explicit project behavior for:

- offline or timeout states;
- authorization expiry;
- unexpected server errors;
- retryable versus non-retryable actions;
- preserving unsaved input.

The server remains the final authority even when Alpine supplies immediate local hints.

---

## 16. Progressive enhancement

A strong htmx design starts with ordinary web behavior.

Prefer:

- real links with `href`;
- real forms with `action` and `method`;
- meaningful routes;
- complete pages for direct visits;
- server-rendered errors and success states.

When one URL returns a complete page for ordinary navigation and a fragment for htmx requests, configure HTTP caching so the variants cannot be mixed. A common server policy is to vary the response on the `HX-Request` request header.

Then add htmx to improve:

- replacement scope;
- perceived continuity;
- request coordination;
- partial navigation;
- live validation;
- polling or streaming;
- multi-region updates.

Alpine enhances immediate interface behavior around that baseline. Neither library should erase the semantics that make the application understandable and resilient.

---

## 17. Common mistakes from an Alpine-first perspective

### Accidental Alpine SPA

Symptoms:

- large entity stores;
- client routing;
- most server responses are JSON;
- database lists rendered with `x-for`;
- business rules in Alpine methods.

Correction: return authoritative HTML and use htmx for server interaction, or explicitly isolate the feature as a rich client application.

### Duplicated form state

Symptoms:

- the input has one value;
- Alpine stores another;
- the request sends a third representation.

Correction: keep a named native control as the submission source and bind Alpine to it.

### Duplicated loading state

Correction: derive ordinary request UI from htmx.

### Competing DOM writers

Correction: assign one owner to each region. Do not let Alpine `x-for` maintain a list htmx also replaces.

### Fetch duplication

Correction: let htmx own HTML-producing requests. Reserve `fetch()` for genuinely non-HTML or specialized operations.

### Hidden event maze

Correction: use domain-oriented event names, one directional flow, and documented timing.

---

## 18. A practical decision sequence

For each behavior, ask:

1. Can semantic HTML do it?
2. Can CSS do it without application state?
3. Is it a specialized browser feature or algorithm? Use plain JavaScript.
4. Must the server be consulted, or does authoritative state change? Use htmx.
5. Is it temporary, immediate, and local? Use Alpine.
6. Does it require both? Separate the local shell from the server target and connect them with native controls or semantic events.
7. Do both appear to own the same state or descendants? Redesign the boundary.

---

## 19. Compact htmx checklist for Alpine developers

Before adding an htmx interaction:

- The server route is meaningful and authorized.
- The full-page or non-enhanced path is defined where practical.
- The request method matches the operation.
- Inputs come from real controls or explicit declared values.
- The target is the smallest coherent server-owned region.
- The swap behavior matches the returned root.
- Alpine state inside the target is intentionally disposable, preserved, or moved outside.
- Loading and disabling derive from htmx request state.
- Request concurrency is specified where races are possible.
- URL behavior is defined for navigable state.
- Success and error responses return usable HTML.
- Cross-boundary reactions use semantic events.
- Focus and accessibility are tested after swaps.

---

## 20. Where to continue

Read the companion guides in this order when building a mixed system:

1. `alpine-for-htmx-developers.md` for the inverse mental model.
2. `alpine-and-htmx-as-coequals.md` for ownership and lifecycle doctrine.
3. `alpine-htmx-superpowers.md` for advanced combined patterns.

The durable mental model is:

> Alpine governs temporary local interaction. htmx governs server communication and HTML replacement. The server remains authoritative, and the target boundary makes that authority visible.
