Event-Driven UI Without A Frontend Store
One of the fastest ways to tangle a UI is to make every component know directly about every other component.
The usual symptom is a controller action or partial response that knows too much about unrelated regions of the page. A change in one place forces edits everywhere else.
This is where events help.
What an event means here
In Swap.Htmx, an event is not just a technical signal. It is a statement that something meaningful happened.
Examples:
- a note was created,
- a cart changed,
- filters were applied,
- a save completed successfully.
Once that event exists, other parts of the UI can react without the initiating action needing to know every detail of those reactions.
Why this matters in server-driven apps
A common misunderstanding is that decoupled UI reactions require a client-side store. They do not.
If the browser can hear that an event occurred and request fresh HTML for the region it owns, you get decoupling without moving the whole application architecture into the browser.
That gives you a useful middle ground:
- controllers stay focused on actions,
- views stay responsible for their own refresh logic,
- new reactive behavior can be added with less controller churn.
When events are the right tool
Use events when several regions care that something happened, but the initiating action should not have to coordinate every update manually.
Good examples:
- a form save should refresh a sidebar summary and an activity feed,
- a list change should refresh a count badge somewhere else,
- a successful operation should trigger a lightweight notification pattern.
A real example from this site
The expense tracker demo uses pure event-driven responses. The controller does not swap any content directly. It appends HX triggers and lets listening regions refresh themselves.
[HttpPost("create")]
public IActionResult Create([FromForm] Expense expense)
{
if (string.IsNullOrWhiteSpace(expense.Description) || expense.Amount <= 0)
{
return BadRequest("Invalid expense details");
}
_service.Add(expense);
Response.Headers.Append("HX-Trigger", $"{TrackerEvents.Expense.Added}, {TrackerEvents.Total.Updated}");
return Ok();
}
Those event keys are kept explicit and readable:
public static class TrackerEvents
{
public static class Expense {
public const string Added = "expense.added";
public const string Deleted = "expense.deleted";
}
public static class Total {
public const string Updated = "total.updated";
}
}
That is a strong example of decoupling: the controller declares what happened, and the UI regions that care can react independently.
When events are not the right tool
If one action only needs to update a few obvious targets immediately, SwapResponse() is often clearer.
Events help most when you want looser coupling, not when you want an excuse to make every interaction indirect.
A simple rule
Let events describe meaningful moments, not arbitrary render steps.
If your event names sound like implementation details instead of user- or domain-level outcomes, the event model is probably becoming too clever.