Heimdall Docs

Modifiers

Modifiers refine trigger behavior without changing the underlying interaction model. They do not define when an interaction exists. They shape how a trigger behaves once it is present.

A trigger says when to invoke. A modifier adjusts how that invocation should behave.

1. What a Modifier Means in Heimdall

Modifiers are small runtime controls expressed on the same DOM boundary as a trigger. They are not new interaction types. They are refinements layered onto an existing trigger.

Trigger   -> when an interaction starts
Modifier -> how that trigger behaves
Payload   -> what data is sent
Swap      -> how returned HTML is applied

2. The Core Pattern

A modifier is most useful when it stays close to the trigger it affects. In Heimdall, that keeps interaction behavior visible on the same element rather than spreading it across separate client-side logic.

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

3. Why Modifiers Matter

Without modifiers, the trigger model would still work, but many interactions would either feel too eager or require extra custom JavaScript. Modifiers let Heimdall stay explicit while still handling common browser-side concerns.

They keep small runtime concerns declarative.
They reduce the need for custom client-side event wiring.
They let the DOM keep expressing interaction behavior directly.
They refine a trigger without turning it into a different programming model.

4. Debounce

Debounce delays invocation until a burst of events settles. This is especially useful for input and change triggers where firing on every event would be unnecessarily noisy.

Strongly Typed Setup

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

Rendered HTML Mental Model

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

In the runtime, input uses a default debounce when one is not supplied, while change only debounces when you opt into it explicitly.

5. Key

The key modifier narrows a keydown trigger to a specific key. This is useful when keyboard input should only invoke the server for one meaningful key rather than for every key press.

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

The runtime accepts either a key name such as Enter or a numeric key code. If no key is specified, the keydown trigger responds to any key.

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

6. Hover Delay

Hover delay prevents a hover-triggered request from firing the moment the pointer crosses a boundary. This is useful for preview UI where a very short pause should be required before content is requested.

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

If the pointer leaves before the delay completes, the scheduled hover invocation is cancelled.

7. Visible Once

The visible trigger often represents a continuation boundary that should fire once and then be replaced. The visible-once modifier controls whether a visible trigger keeps firing after the first observation.

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

8. Scroll Threshold

Scroll threshold defines how close a scroll region must get to its end before the scroll trigger fires. This keeps scroll-driven interactions from waiting until the exact bottom boundary is reached.

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

This is best understood as a prefetch boundary. The trigger fires near the end, not only at the exact end.

9. Prevent Default

Prevent default controls whether the browser’s built-in behavior should be suppressed before Heimdall runs the action. This matters most for anchors, submits, and some keyboard-driven flows.

Strongly Typed Setup

link.Heimdall()
    .Click("Nav.LoadSection")
    .PreventDefault()
    .Target("#content")
    .SwapInner();

Rendered HTML Mental Model

<a
  href="/reports"
  heimdall-content-click="Nav.LoadSection"
  heimdall-prevent-default="true"
  heimdall-content-target="#content"
  heimdall-content-swap="inner">
  Reports
</a>

The runtime already defaults this on for submits and for anchor clicks, and it will also default to preventing Enter on certain keydown flows. The modifier matters when you want to make that behavior explicit or override it.

10. Disable During Request

Disable marks an element as disabled while its request is in flight. This helps guard against duplicate interaction and gives the browser a simple busy state while the server responds.

button.Heimdall()
    .Click("Orders.Submit")
    .PayloadFromClosestForm()
    .Disable()
    .SwapOuter();

The runtime already defaults click and submit triggers to disabling during the request. Other triggers default to not doing that unless you opt in.

<button
  heimdall-content-click="Orders.Submit"
  heimdall-payload="closest-form"
  heimdall-content-disable="true"
  heimdall-content-swap="outer">
  Submit Order
</button>

11. Trigger and Modifier Work Together

A modifier only makes sense in relation to a trigger. Debounce modifies input or change. Key modifies keydown. Hover delay modifies hover. Visible once modifies visible. Scroll threshold modifies scroll.

Trigger + Modifier
-> defines a refined invocation boundary
-> without creating a new interaction model

12. Runtime Defaults Matter

Some modifiers are only explicit overrides of behavior the runtime already has defaults for. Understanding those defaults helps explain why some elements behave well without additional configuration.

Input uses a default debounce when one is not supplied.
Hover uses a default delay when one is not supplied.
Visible defaults to firing once unless configured otherwise.
Click and submit default to disabling the element during the request.
Anchors and submits default to preventing browser navigation or form submission while Heimdall handles the action.

13. What This Page Intentionally Does Not Cover

Polling also uses an attribute-level control, but it is better understood as repeated runtime-driven invocation over time rather than as a simple trigger modifier. It deserves its own page.

Modifiers refine a trigger.
Polling introduces repeated invocation over time.

14. Common Mental Model

A good way to think about modifiers is that they tune the browser’s participation in the interaction without moving ownership away from the server.

Server still owns:
- the action
- the returned HTML
- the UI truth

Browser modifiers only shape:
- timing
- filtering
- request suppression
- in-flight behavior

15. When to Use Which Modifier

Each modifier fits a different kind of interaction refinement.

Debounce         -> wait for input/change to settle
Key              -> restrict keydown to a meaningful key
Hover Delay      -> require pointer dwell before invoking
Visible Once     -> fire visible trigger one time
Scroll Threshold -> prefetch before exact scroll end
Prevent Default  -> suppress native browser behavior
Disable          -> prevent duplicate requests in flight

Next Steps

Once modifiers are clear, the next step is understanding repeated runtime-driven invocation and then how request data is gathered when those interactions actually run.