Heimdall Docs

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:

1. The server renders a host element with state attached.
2. A child interaction uses the closest state as its payload.
3. The server receives that state in a DTO.
4. The server returns updated HTML with updated state.
5. Heimdall swaps the updated host back into the DOM.

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.

The server owns the truth.
The DOM carries the current interaction boundary.
The next response re-renders the current 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.