# Day 1: Start Here — Ownership, Boundaries, and the Web-Native Mental Model

**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

Establish the architectural rules that prevent Alpine and htmx from becoming competing frameworks. This is the startup primer: installation, vocabulary, decision rules, and the smallest useful examples.

## 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

By the end of the day, you should be able to:

- explain what Alpine, htmx, the server, HTML, CSS, URLs, native controls, and plain JavaScript each own;
- classify a feature as Alpine-only, htmx-only, mixed, or a specialized JavaScript island;
- draw a replacement boundary before writing `hx-target`;
- decide whether local state should reset or survive a server update;
- recognize the most common duplicated-authority failures.

## 1. The operating model

The libraries are peers, not substitutes:

| Layer | Default responsibility |
| --- | --- |
| Server and database | Durable truth, authorization, validation, transactions, workflow state |
| Server templates | Canonical HTML representations of server truth |
| URL and routes | Navigable, shareable, refresh-safe locations |
| HTML | Structure, native controls, forms, links, semantics, accessibility relationships |
| CSS | Layout, presentation, ordinary transitions, request-state styling |
| htmx | HTTP requests, trigger timing, request coordination, history integration, targeted swaps |
| Alpine | Immediate browser-local state such as open/closed, selected, focused, provisional, or dismissed |
| Plain JavaScript | Browser APIs, nontrivial algorithms, rich widgets, high-frequency rendering |
| Browser events | Contracts between independently owned regions |

The team convention is:

> Share a workflow, but do not share authority over the same state value or the same DOM descendants.

This is an architectural convention, not a library limitation. Both libraries are technically capable of doing more. The convention keeps the system understandable under refresh, failure, history restoration, and repeated swaps.

## 2. Two constitutional rules

### Rule A: one authoritative owner per state value

A value may have drafts, projections, or cached representations, but one place must decide what is true.

Example: a cart line quantity.

- The native input contains the proposed quantity.
- Alpine may display a provisional subtotal.
- htmx submits the input.
- The server checks inventory, price, discounts, tax, and authorization.
- The returned HTML is the canonical cart representation.

The Alpine subtotal is a preview. It is not a second cart engine.

Use this ownership table:

| Meaning or lifetime | Default owner |
| --- | --- |
| Durable, shared, financial, permission-sensitive | Server |
| Bookmarkable or meaningful after refresh | URL and route |
| Required in a request | Named native control or an explicit htmx parameter |
| Exists for one local interaction | Alpine |
| Exists only while a request is in flight | htmx lifecycle plus CSS |
| Browser-only preference | Browser storage, optionally surfaced through Alpine |
| Complex rich-widget internals | Plain JavaScript island |

Use the refresh test:

> If losing the value on refresh would make the application incorrect or conceal real state, Alpine must not be its only authority.

### Rule B: one intentional writer per DOM boundary

For every meaningful region, choose one current writer:

1. htmx-owned and replaceable;
2. Alpine-owned and stable;
3. a stable Alpine shell with an htmx-owned interior;
4. an htmx-replaceable component containing disposable Alpine behavior;
5. an isolated JavaScript island;
6. static or native HTML requiring neither library.

The most damaging integration bugs are usually boundary bugs, not syntax bugs.

## 3. Installation SOP

A production project should use pinned or self-hosted assets according to its dependency policy. A minimal local layout is:

```html
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Alpine + htmx Training</title>

    <style>
        [x-cloak] { display: none !important; }
        .htmx-indicator { display: none; }
        .htmx-request .htmx-indicator,
        .htmx-request.htmx-indicator { display: inline; }
    </style>

    <script src="/vendor/htmx-2.0.10.min.js" defer></script>
    <script src="/vendor/alpine-3.15.12.min.js" defer></script>
</head>
<body>
    <main>
        <!-- exercises -->
    </main>
</body>
</html>
```

Operational rules:

1. Pin versions; do not depend on an unversioned CDN URL in production.
2. Use `defer` when loading the script-tag builds.
3. Add `[x-cloak]` before writing initially hidden Alpine UI.
4. Confirm the server can return complete pages and target-specific fragments.
5. Record the htmx major version in the repository because htmx 4 changes defaults and event names.
6. Load Alpine plugins before Alpine initializes, following the plugin’s official installation instructions.
7. Run Alpine initialization only once when using a bundled module build.

## 4. Smallest Alpine-only operation

Use Alpine when the interaction is immediate, local, and safe to discard.

```html
<section x-data="{ open: false }">
    <button
        type="button"
        @click="open = !open"
        :aria-expanded="open"
        aria-controls="local-details"
    >
        Toggle details
    </button>

    <div id="local-details" x-cloak x-show="open">
        This content is already in the document.
    </div>
</section>
```

Ownership:

- `open`: Alpine;
- button and relationship: HTML;
- visibility styling: Alpine state reflected through DOM/CSS;
- durable state: none.

A request would add latency and server load without adding authority.

## 5. Smallest htmx-only operation

Use htmx when the browser needs a server representation.

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

<section id="customer-panel" aria-live="polite"></section>
```

Expected fragment:

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

Ownership:

- customer truth: server;
- representation: server template;
- request and installation: htmx;
- target contents: htmx;
- non-enhanced route: the real `href`.

The target should be the smallest coherent server-owned region.

## 6. Preferred mixed shape: stable shell, replaceable interior

Use this when local presentation must survive server refreshes.

```html
<aside
    x-data="{ open: false }"
    @cart-updated.window="open = true"
>
    <button
        type="button"
        @click="open = !open"
        :aria-expanded="open"
        aria-controls="cart-drawer"
    >
        Cart
    </button>

    <section id="cart-drawer" x-cloak x-show="open" x-transition>
        <div id="cart-contents">
            <!-- server-rendered cart -->
        </div>
    </section>
</aside>

<button
    type="button"
    hx-post="/cart/items/42"
    hx-target="#cart-contents"
    hx-swap="outerHTML"
>
    Add item
</button>
```

The response must return an element whose root matches the chosen swap:

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

The outer `<aside>` is not targeted. Therefore its Alpine state survives. The server may emit a semantic `cart-updated` event after success; event mechanics are covered on Day 7.

## 7. Correct disposable shape

Use a replaceable Alpine root when reset is desirable.

```html
<article id="customer-42" x-data="{ menuOpen: false }">
    <button type="button" @click="menuOpen = !menuOpen">
        Actions
    </button>

    <div x-cloak x-show="menuOpen">
        Temporary row actions
    </div>

    <button
        type="button"
        hx-get="/customers/42/edit"
        hx-target="#customer-42"
        hx-swap="outerHTML"
    >
        Edit
    </button>
</article>
```

The returned editor replaces the entire article. Losing `menuOpen` is correct because that state described the old representation.

## 8. Decision SOP for every new interaction

Before adding either library, execute this sequence:

1. **Can semantic HTML do it?** Use a link, form, disclosure pattern, `<dialog>`, native validation, or ordinary control where practical.
2. **Can CSS do it without application state?** Prefer CSS for layout, responsive behavior, hover, focus, and ordinary transitions.
3. **Is this a specialized browser algorithm?** Use a small JavaScript module or isolated widget.
4. **Must the server be consulted or durable state change?** Use htmx.
5. **Is it immediate, temporary, and local?** Use Alpine.
6. **Does it need both?** Separate the Alpine shell from the htmx target and connect them through controls or semantic events.
7. **Do both appear to write the same descendants or own the same value?** Stop and redesign.

Write the answer in a component contract before implementation.

## 9. First-day component contract

For a simple feature, record:

```text
Feature:
Durable state owner:
URL state owner:
Submitted values:
Alpine-local values:
htmx trigger:
htmx target:
Swap strategy:
Should Alpine state reset or survive?
Focus behavior:
Failure representation:
```

Example:

```text
Feature: customer edit drawer
Durable state owner: server/database
URL state owner: none
Submitted values: named form controls
Alpine-local values: drawer open/closed
htmx trigger: click Edit; form submit
htmx target: #customer-editor-body
Swap strategy: innerHTML for load; outerHTML for form
Should Alpine state reset or survive? drawer survives validation swaps
Focus behavior: focus first field after content-loaded event; restore trigger on close
Failure representation: full server-rendered form with values and errors
```

## 10. Anti-pattern recognition drill

### Shadow backend

```html
<div x-data="{ users: [], permissions: {}, cart: {} }">
```

This is suspicious when the values mirror durable server entities and business rules.

### Request for local trivia

```html
<button hx-get="/open-menu">Open menu</button>
```

A menu that already exists locally should not require a request.

### Two writers

```html
<ul id="orders" x-data="{ orders: [] }">
    <template x-for="order in orders">...</template>
</ul>

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

Alpine and htmx both claim the collection. Choose one writer.

### Broad destructive target

```html
<body x-data="{ sidebarOpen: true }">
    <button hx-get="/report" hx-target="body">Load report</button>
</body>
```

This destroys unrelated state and complicates focus, history, and initialization. Target a report region or intentionally perform navigation.

## 11. Day lab: classify and repair

Create a page containing these four features:

1. a local FAQ disclosure;
2. a server-loaded customer card;
3. a cart drawer whose contents update without closing;
4. an inline editor whose menu state resets after the row is replaced.

For each feature:

- label the Alpine root with an HTML comment;
- label the htmx target with an HTML comment;
- write the state owner beside each state value;
- explain in one sentence why refresh is safe or unsafe;
- test the intended reset/survival behavior by manually replacing the target in DevTools.

## Mini-tasks

### Task A: ownership sorting

Assign an owner to each value:

- selected density preference;
- applied search query;
- typed but unsubmitted note;
- current invoice balance;
- whether a dropdown is open;
- whether a save request is in flight;
- drag ghost coordinates;
- current authenticated role.

### Task B: boundary repair

A modal root contains `x-data="{ open: true }"` and is also the `hx-target` for every validation response. Change the markup so the shell survives while the form is replaced.

### Task C: progressive baseline

Turn an htmx-only button into a real link or form that still works when JavaScript is unavailable.

## Comprehension check

Answer without looking back:

1. What does `x-data` define?
2. What does `hx-target` define beyond “where to put HTML”?
3. When is replacing an Alpine root correct?
4. Why should server-required values live in native controls?
5. Who owns a request’s factual in-flight state?
6. What is the refresh test?
7. Name the two preferred mixed component shapes.
8. Why is a global Alpine store not the default communication mechanism?
9. What must a server return for an `outerHTML` swap?
10. What is the first repair when preservation or morphing seems necessary?

## Cheat sheet

```text
LOCAL + TEMPORARY                  -> Alpine
SERVER / DURABLE / SHARED         -> server reached through htmx
BOOKMARKABLE                      -> URL
SUBMITTED                         -> named control
REQUEST IN FLIGHT                 -> htmx lifecycle + CSS
SPECIALIZED ALGORITHM             -> plain JS island

STATE MUST SURVIVE SWAP            -> Alpine root outside target
STATE SHOULD RESET                -> replace Alpine root
BOTH WRITE SAME DESCENDANTS       -> redesign
```

## Exit exercise

Design a “notification center” on paper. The server owns notification records and unread count. The panel may stay open while records refresh. A toast may appear after a new notification arrives. Identify every owner, target, event, and reset/survival decision. Do not write code until the contract has no duplicated authority.

## Exit criteria

You pass Day 1 when you can review an unfamiliar component and identify:

- every durable, URL, control, request, and local state value;
- the mutation owner of each region;
- whether each Alpine root should reset or survive;
- whether the non-enhanced path is meaningful.

## 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.
