Alpine.js integration¶
Alpine.js is bundled for client-side interactivity that doesn't need a server round-trip.
For a full walkthrough with examples, see Working with HTMX and Alpine.
When to use Alpine¶
| Use Alpine for | Use HTMX/@action for |
|---|---|
| Toggle visibility | Persisting data |
| Focus management | Validation with server rules |
| Animations | Lists, counters, form state |
| Client-only filters | Anything that must survive refresh |
Seeding Alpine state¶
Override get_client_state() on your component:
<div {% shard_alpine component %}>
<button @click="open = !open">Menu</button>
<div x-show="open" x-transition>{{ slots.default|safe }}</div>
</div>
shard_alpine tag¶
Outputs:
Pass extra keys:
Combining Alpine and HTMX¶
Alpine handles immediate UI feedback. HTMX handles persistence:
<div {% shard_alpine component %}>
<form {% shard_htmx component "save" %} @submit.prevent="$el.requestSubmit()">
<input
name="draft"
@focus="focused = true"
@blur="focused = false"
/>
<button type="submit" :disabled="!focused">Save</button>
</form>
</div>
Listening for Shrd events¶
Action responses trigger HTMX events. Listen with Alpine:
Or in shard.js / your own scripts:
Loading order¶
{% shard_scripts %} loads:
htmx.min.js(defer)alpine.min.js(defer)shard.js(defer)
All scripts use defer, so order is preserved and DOM is ready.
Example: client-only dropdown¶
Alpine handles open/close with no server round-trip:
class Dropdown(Component):
template_name = "components/dropdown.html"
def get_client_state(self) -> dict:
return {"open": False}
{% load shard %}
<div {% shard_root component %} {% shard_alpine component %}>
<button type="button" @click="open = !open">Menu</button>
<div x-show="open" x-transition @click.outside="open = false">
{{ slots.default|safe }}
</div>
</div>
{% shard_alpine component %} outputs x-data='{"open": false}' from get_client_state().
Example: TodoList (Alpine + HTMX together)¶
The example app combines both tools on one root element:
<div {% shard_root component %} {% shard_alpine component %}>
<form {% shard_htmx component "add_item" %} @submit.prevent="$el.requestSubmit()">
<input
name="text"
{% shard_htmx component "set_draft" trigger="keyup changed delay:300ms" %}
@focus="focused = true"
@blur="focused = false"
/>
<button type="submit">Add</button>
</form>
</div>
| Layer | Responsibility |
|---|---|
Alpine (focused) |
Immediate focus UI |
HTMX (set_draft) |
Debounced sync of draft text to server |
HTMX (add_item) |
Persist new item on submit |
Full walkthrough: Working with HTMX and Alpine. Source: example/templates/components/todo_list.html.
Example: listening for HTMX events¶
When another component's action fires @emits("todo:added"), react in Alpine:
The .window modifier listens for events HTMX dispatches on document.
Shared and global state¶
{% shard_alpine %} seeds per-component x-data from get_client_state(). Shrd does not ship a global Alpine store.
| Need | Use |
|---|---|
| Persisted data | Server state + HTMX |
| One component's UI chrome | get_client_state() |
| Cross-component signals | HTMX events (@emits, @event.window) |
| Page layout (sidebar, theme) | Page x-data from Django or Alpine.store() in your layout script |
Full guidance and examples: Shared and global client state.
No build step¶
Alpine is included as a static file. You write directives directly in Django templates. No x-data compilation or component registration on the client.