State
Heimdall keeps interaction state close to the DOM that owns it. The server renders state into HTML, the browser sends that state back when needed, and the server returns the next UI state as fresh HTML.
This keeps the server as the source of truth while still enabling rich interactive flows.
1. What State Means in Heimdall
In Heimdall, state is often rendered directly onto a DOM boundary. Interactions can pull payload data from the closest state container instead of relying on a separate client-side store.
<div id="counter-host" data-heimdall-state='{"count":1}'>
...
</div>2. Strongly Typed State Markup
The strongly typed helpers emit ordinary HTML attributes. This lets you author stateful boundaries in C# while keeping the runtime model HTML-first.
host.Heimdall()
.State(new CounterState
{
Count = 1
});3. The Basic Loop
A common Heimdall state flow looks like this:
4. Counter Example
This is the clearest example of the Heimdall state model: a host owns state, a button reads from that state, and the server returns a refreshed host.
Rendered HTML
<div id="counter-host" data-heimdall-state='{"count":1}'>
<div>Count: 1</div>
<button
heimdall-click="Counter.Increment"
heimdall-payload="closest-state"
heimdall-target="#counter-host"
heimdall-swap="outer">
Increment
</button>
</div>Action DTO
public sealed class CounterState
{
public int Count { get; set; }
}Action
[ContentInvocation]
public static IHtmlContent Increment(CounterState state)
{
state.Count++;
return CounterHost(state);
}5. Closest State Payloads
When an interaction uses closest-state, Heimdall walks up the DOM to find the nearest state boundary and sends that value as the request payload.
button.Heimdall()
.Click("Counter.Increment")
.PayloadFromClosestState()
.Target("#counter-host")
.SwapOuter();6. Why Outer Swap Often Pairs with State
When state is attached to the host element, the returned HTML often needs to replace that host entirely. Outer swap makes that natural.
button.Heimdall()
.Click("Counter.Increment")
.PayloadFromClosestState()
.Target("#counter-host")
.SwapOuter();7. Keyed State
Heimdall also supports keyed state boundaries. This is useful when a DOM region carries more than one state value or when a child interaction should bind to a specific state source.
<div data-heimdall-state-weather='{"page":2}'>
...
</div>8. Strongly Typed Keyed State
The strongly typed helpers can emit keyed state and can also target a keyed closest-state payload source.
host.State("weather", new WeatherState
{
Page = 2
});
button.Heimdall()
.Click("Weather.NextPage")
.PayloadFromClosestState("weather")
.Target("#weather-host")
.SwapOuter();9. What State Is Not
Heimdall state is not a separate client-side store. It is not a long-lived front-end state container trying to become the source of truth.
10. Common Pattern: Stateful Host Boundary
A common pattern is to create a host element that owns both display and state. Child interactions live inside that host and read from its nearest state boundary.
<div id="widget-host" data-heimdall-state='{"page":1,"filter":"all"}'>
...
</div>Next Steps
Once state clicks, forms and lazy loading become much easier to understand.