With htmx

htmx swaps server HTML into the page. Aegis makes parts of that HTML alive on the client. They are a good pair: htmx for navigation and fragments where the server can render every state, Aegis islands for the pieces that need client state, validation or optimistic updates. A swap does not break an island, and an island that arrives with a swap starts by itself.

Islands inside swapped fragments#

<button hx-get="/cart/items" hx-target="#cart" hx-swap="innerHTML">Refresh</button>
<div id="cart"></div>

<script type="module">
import { island, hydrate } from 'aegis';

island('qty', ({ props, signal, html }) => {
    const qty = signal(props.qty);
    return html`<input type="number" bind:value=${qty}> ${() => qty.value * props.price} €`;
}, { types: { qty: Number, price: Number } });

hydrate(document, { watch: true });   // every fragment htmx inserts is scanned; islands mount, removed ones are destroyed
</script>

The server returns <div data-aegis="qty" data-qty="2" data-price="9">…</div> inside the fragment; hydrate(document, { watch: true }) sees it arrive through a MutationObserver and mounts it. When htmx removes the fragment, the island's scope is disposed: listeners, timers and requests go with it.

Without watch, call hydrate(evt.detail.target) in htmx's htmx:afterSwap event.

Morph instead of replace#

hx-swap="innerHTML" replaces the nodes, so an island in the target is destroyed and recreated. When the fragment contains state you want to keep (an open dropdown, a focused input, a running island), use Aegis's own swap with morph for that target:

import { swap } from 'aegis';
swap(document.querySelector('#cart'), await fetch('/cart/items'), { mode: 'morph' });   // diffs and patches; islands and focus survive

Or keep htmx and add the idiomorph extension; Aegis islands survive a morph either way because their host element is kept.

What htmx does that Aegis does not replace#

  • Server-driven navigation with hx-boost: boost() in Aegis does the same job, but if htmx already drives the page, keep it.
  • Request attributes on plain elements (hx-get on a link): Aegis has no attribute-driven requests; it has resource() and mutation() in components.

What Aegis adds to an htmx page#

  • Forms with client validation and 422 mapping: wireForm(form) on the server-rendered form; the same form still submits without JavaScript, and htmx can still drive the submit.
  • Optimistic updates: a like button, a quantity field, a reorder, without a round-trip before the UI moves.
  • Client state that the server should not render: a filter with 10 000 rows already on the page, a wizard's draft, a chart.