Skip to documentation
Heimdall Docs
Heimdall Docs

Triggers

Triggers define when a Heimdall interaction begins. They are the browser-side start of the request, response, and swap cycle.

A trigger answers one question only: when should this element invoke the server?

1. What a Trigger Means in Heimdall

A trigger is the event boundary that starts an interaction. It does not define payload shape, response structure, or swap behavior by itself. It simply tells the runtime when to begin.

Text
Trigger
-> payload is gathered
-> action is invoked
-> HTML is returned
-> swap is applied

2. The Core Pattern

In Heimdall, triggers are always attached through the behavior layer. That keeps HTML structure and Heimdall interaction behavior conceptually separate.

Text
button.Heimdall()
    .Click("Counter.Increment")
    .PayloadFromClosestState()
    .SwapOuter();

3. Why Triggers Matter

Heimdall interactions are built from small, explicit pieces. The trigger is the first of those pieces. It gives the browser a precise point at which server-driven UI should continue.

A trigger starts the interaction.
Payload binding decides what data is sent.
The action decides what HTML to return.
The swap decides how that HTML is applied.

4. Click

The click trigger is the most common starting point. It is a natural fit for buttons, links, and small interaction surfaces that should invoke the server when selected.

Fluent C#

Text
button.Heimdall()
    .Click("Counter.Increment")
    .PayloadFromClosestState()
    .SwapOuter();

Rendered HTML

HTML
<button
  heimdall-content-click="Counter.Increment"
  heimdall-payload-from="closest-state"
  heimdall-content-swap="outer">
  Increment
</button>

5. Submit

Submit is the form-native trigger. It works especially well when the form itself is the payload boundary and when native browser validation should remain part of the interaction.

Fluent C#

Text
form.Heimdall()
    .OnSubmit("Contact.Submit")
    .PayloadFromClosestForm()
    .SwapNone();

This is one of the clearest examples of Heimdall working with normal HTML rather than replacing it.

Rendered HTML

HTML
<form
  heimdall-content-submit="Contact.Submit"
  heimdall-payload-from="closest-form"
  heimdall-content-swap="none">
  ...
</form>

6. Input

The input trigger fires as the user types. It is useful for live search, server-driven suggestions, and immediate validation feedback.

Fluent C#

Text
input.Heimdall()
    .Input("Search.Query")
    .PayloadFromClosestForm()
    .Target("#search-results")
    .SwapInner();

Rendered HTML

HTML
<input
  heimdall-content-input="Search.Query"
  heimdall-payload-from="closest-form"
  heimdall-content-target="#search-results"
  heimdall-content-swap="inner">

Because input can fire frequently, it is usually best reserved for interactions where continuous feedback is worth the extra request volume.

7. Change

The change trigger is lower-frequency than input. It is a good fit when the interaction should wait until a selection or value change is committed.

Fluent C#

Text
select.Heimdall()
    .Change("Reports.Filter")
    .PayloadFromClosestForm()
    .Target("#report-results")
    .SwapInner();

Rendered HTML

HTML
<select
  heimdall-content-change="Reports.Filter"
  heimdall-payload-from="closest-form"
  heimdall-content-target="#report-results"
  heimdall-content-swap="inner">
  ...
</select>

This often works well for select elements, toggles, or filter controls where per-keystroke updates are unnecessary.

8. Keydown

The keydown trigger fires in response to keyboard input. It is useful when a keypress itself is the interaction boundary rather than a full field change or submit.

Fluent C#

Text
input.Heimdall()
    .KeyDown("Search.Accept")
    .PayloadFromClosestForm()
    .Target("#search-results")
    .SwapInner();

Rendered HTML

HTML
<input
  heimdall-content-keydown="Search.Accept"
  heimdall-payload-from="closest-form"
  heimdall-content-target="#search-results"
  heimdall-content-swap="inner">

This is often useful for keyboard-driven UI, quick actions, or flows where specific keys should initiate a request.

9. Blur

The blur trigger fires when an element loses focus. It is a natural fit for validation or post-edit checks that should happen after the user leaves a field.

Fluent C#

Text
input.Heimdall()
    .Blur("Profile.ValidateDisplayName")
    .PayloadFromClosestForm()
    .Target("#display-name-validation")
    .SwapInner();

Rendered HTML

HTML
<input
  heimdall-content-blur="Profile.ValidateDisplayName"
  heimdall-payload-from="closest-form"
  heimdall-content-target="#display-name-validation"
  heimdall-content-swap="inner">

This keeps feedback tied to the end of a field interaction rather than to every keystroke.

10. Hover

The hover trigger fires when an element is hovered. It can be useful for previews, hints, or secondary content that should appear only when the user pauses over a boundary.

Fluent C#

Text
div.Heimdall()
    .Hover("Products.Preview")
    .PayloadFromClosestState("product")
    .Target("#product-preview")
    .SwapInner();

Rendered HTML

HTML
<div
  heimdall-content-hover="Products.Preview"
  heimdall-payload-from="closest-state:product"
  heimdall-content-target="#product-preview"
  heimdall-content-swap="inner">
  ...
</div>

Hover is best used intentionally, especially when preview content is valuable but should not be loaded up front.

11. Visible

The visible trigger fires when an element becomes visible in the viewport. This is the foundation of lazy loading and continuation-boundary patterns.

Fluent C#

Text
sentinel.Heimdall()
    .Visible("Weather.LoadMore")
    .PayloadFromClosestState("weather")
    .Target("#weather-sentinel")
    .SwapOuter();

Rendered HTML

HTML
<div
  heimdall-content-visible="Weather.LoadMore"
  heimdall-payload-from="closest-state:weather"
  heimdall-content-target="#weather-sentinel"
  heimdall-content-swap="outer">
  ...
</div>

12. Scroll

The scroll trigger fires as a scroll boundary is reached. It is useful when interaction should be tied to scroll position rather than simple visibility.

Fluent C#

Text
div.Heimdall()
    .Scroll("Activity.LoadEarlier")
    .PayloadFromClosestState("activity")
    .Target("#activity-stream")
    .SwapInner();

Rendered HTML

HTML
<div
  heimdall-content-scroll="Activity.LoadEarlier"
  heimdall-payload-from="closest-state:activity"
  heimdall-content-target="#activity-stream"
  heimdall-content-swap="inner">
  ...
</div>

This can be useful for feeds, activity timelines, or UI that should react to scrolling through a region.

13. Load

The load trigger fires when an element is loaded into the DOM. It is useful when content should be requested immediately as part of initial rendering, but still through the normal Heimdall action pipeline.

Fluent C#

Text
div.Heimdall()
    .Load("Dashboard.Summary")
    .Target("#summary-panel")
    .SwapInner();

Rendered HTML

HTML
<div
  heimdall-content-load="Dashboard.Summary"
  heimdall-content-target="#summary-panel"
  heimdall-content-swap="inner">
</div>

This can be useful for panels, widgets, or deferred sections that should begin empty and hydrate through a standard action.

14. Document Visible

The document-visible trigger fires whenever the document transitions from hidden to visible, such as when a user returns to the tab. It does not fire during initial page boot; combine it with Load when both moments should refresh.

Fluent C#

Text
dashboard.Heimdall()
    .DocumentVisible("Dashboard.Refresh")
    .PayloadEmptyObject()
    .Target("#dashboard")
    .SwapInner();

Rendered HTML

HTML
<div
  heimdall-content-document-visible="Dashboard.Refresh"
  heimdall-payload="{}"
  heimdall-content-target="#dashboard"
  heimdall-content-swap="inner">
</div>

15. Online

The online trigger fires when the browser reports that connectivity has returned. It is a signal to attempt a normal action; it does not guarantee that the application server is reachable.

Fluent C#

Text
connectionPanel.Heimdall()
    .Online("Connection.Restore")
    .PayloadEmptyObject()
    .Target("#connection-panel")
    .SwapInner();

Rendered HTML

HTML
<div
  heimdall-content-online="Connection.Restore"
  heimdall-payload="{}"
  heimdall-content-target="#connection-panel"
  heimdall-content-swap="inner">
</div>

16. Offline Is a Client Event

Offline cannot honor the server-invocation trigger contract. Heimdall therefore emits a client-only document event and never makes or queues a request for it.

JavaScript
document.addEventListener("heimdall:offline", event => {
  console.log(event.detail.online); // false
  showOfflineBanner();
});

17. Trigger Does Not Mean Behavior by Itself

A trigger does not fully describe an interaction. It only defines the moment the interaction begins. The rest still comes from payload binding, target selection, and swap rules.

Text
Trigger = when
Payload = what data is sent
Target = where response lands
Swap = how response is applied

18. Why Heimdall Keeps Triggers Explicit

Heimdall stays clear by making interaction boundaries visible in the markup. Instead of hiding behavior behind a large client abstraction, the DOM itself expresses what event starts the request.

The interaction start is visible on the element.
The trigger does not need its own client-side state system.
The same trigger model works across buttons, forms, sentinels, deferred regions, and keyboard-driven UI.
The browser remains a small runtime rather than a second application.

19. When to Use Which Trigger

Each trigger fits a different interaction shape.

Text
Click    -> buttons, links, explicit actions
Submit   -> forms, native validation, traditional submits
Input    -> live search, instant validation, live suggestions
Change   -> committed field updates, select controls, filters
Keydown  -> keyboard-driven actions, key-based flows
Blur     -> post-edit validation, field completion checks
Hover    -> previews, hints, deferred secondary UI
Visible  -> element visibility, lazy loading, continuation boundaries
DocumentVisible -> refresh when returning to a hidden tab
Online   -> retry or refresh when browser connectivity returns
Scroll   -> feed or timeline interaction tied to scroll position
Load     -> deferred initial content, startup widgets