Topics ⇆ HTMX Bridge
Topics and htmx meet in the core: an element can fire an htmx request when a Topic publishes, and an element can publish to a Topic after its swap. Put the two together and you get a full refresh loop with no hand-written JavaScript — two HTML attributes.
Topic → htmx: hx-trigger="topic:<name>"
Give an element hx-trigger="topic:<name>" and it runs its htmx request every time anything publishes to that Topic. Here a cart-summary panel refetches itself whenever the cart Topic changes:
<!-- Refetch the cart summary whenever ANYTHING publishes to the "cart" Topic. -->
<div
hx-get="/cart/summary"
hx-trigger="topic:cart"
hx-swap="innerHTML"
>
<!-- server-rendered cart summary lands here -->
</div>Want to react to a single field? Use topic:<key>:<field> — for example topic:cart:qty. Under the hood, a Topic publish dispatches a gothic:topic:<name> DOM CustomEvent on document, and the alias maps the topic: trigger prefix onto it, so the wiring stays declarative.
<!-- Listen to a single field with topic:<key>:<field>. -->
<div hx-get="/cart/count" hx-trigger="topic:cart:qty" hx-swap="innerHTML"></div>
<!-- Under the hood a Topic publish dispatches a DOM CustomEvent on document:
gothic:topic:cart (whole-struct publish)
gothic:topic:cart:qty (single-field publish)
The alias maps the "topic:" trigger prefix onto "gothic:topic:", so you
write topic:cart and htmx listens for gothic:topic:cart. No JS. -->htmx → Topic: hx-publish="topic:<name>"
The other direction: give an element hx-publish="topic:<name>" and, after its htmx swap completes, it publishes to that Topic. It is notify-style — it broadcasts the name and carries no value, which is exactly what a subscriber's hx-trigger="topic:<name>" is waiting for.
<!-- After this button's htmx swap completes, publish to the "cart" Topic.
hx-publish is notify-style: it broadcasts the name, it carries no value. -->
<button
hx-post="/cart/add"
hx-swap="outerHTML"
hx-publish="topic:cart"
>
Add to cart
</button>The complete no-JS loop
Wire a publisher and a subscriber to the same Topic name and the loop closes on its own: the button adds an item and publishes cart; the summary hears cart and refetches. Any number of panels can subscribe to the same Topic — they all refresh together.
<!-- A complete refresh loop, no hand-written JavaScript. -->
<!-- Publisher: broadcasts "cart" after it adds an item. -->
<button hx-post="/cart/add" hx-swap="outerHTML" hx-publish="topic:cart">
Add to cart
</button>
<!-- Subscriber: refetches itself whenever "cart" is published. -->
<div hx-get="/cart/summary" hx-trigger="topic:cart" hx-swap="innerHTML">
<!-- cart summary -->
</div>Ready to share code across WASM pages and render templ components client-side? That's next!
