Hyper · htmx-style

Triggers & debouncing

Fire on any event, and debounce it in the attribute: kj-trigger="keyup delay:400ms" turns an input into live, server-validated UI.

Without kj-trigger, elements use the natural event: submit for forms, change for inputs and selects, click for everything else. Name any DOM event to override it, and add delay:Nms to wait until the user pauses — one request per pause instead of one per keystroke.

Three special triggers handle timing rather than events: load (immediately), revealed (when scrolled into view) and every Ns (polling). They have their own pages: lazy loading and polling.

Live · Validation, search and select — all server-driven

Try “admin”, “ab”, or your name.

    All three call PHP: validate.php, search.php, tab.php. Zero lines of JavaScript.

    View the source for this demo
    HTML
    <div class="grid g2">
      <div class="field">
        <label for="tr-user">Username <small class="muted">· keyup delay:400ms</small></label>
        <input id="tr-user" name="username" type="text" autocomplete="off" spellcheck="false"
               kj-get="/api/validate.php" kj-trigger="keyup delay:400ms" kj-target="#tr-user-msg"
               aria-describedby="tr-user-msg">
        <p id="tr-user-msg" aria-live="polite" style="margin:4px 0 0;font-size:14.5px">Try “admin”, “ab”, or your name.</p>
      </div>
      <div class="field">
        <label for="tr-q">Package search <small class="muted">· keyup delay:250ms</small></label>
        <input id="tr-q" name="q" type="search" autocomplete="off"
               kj-get="/api/search.php" kj-trigger="keyup delay:250ms" kj-target="#tr-results">
        <ul id="tr-results" class="list-plain" aria-live="polite" style="font-family:var(--mono);font-size:14px"></ul>
      </div>
      <div class="field">
        <label for="tr-tab">Module <small class="muted">· default trigger: change</small></label>
        <select id="tr-tab" name="tab" kj-get="/api/tab.php" kj-target="#tr-tab-out">
          <option value="core">Core</option><option value="hyper">Hyper</option><option value="pulse">Pulse</option><option value="motion">Motion</option>
        </select>
        <div id="tr-tab-out" aria-live="polite" style="margin-top:8px"></div>
      </div>
    </div>

    More examples

    Trigger cheatsheet

    HTML
    <input name="q" kj-get="/search" kj-trigger="keyup delay:300ms" kj-target="#hits">
    <div kj-get="/preview" kj-trigger="mouseenter" kj-target="#card">Hover</div>
    <form kj-post="/save" kj-trigger="change">…autosave on any field change…</form>
    <div kj-get="/widgets/weather" kj-trigger="load"></div>
    <div kj-get="/comments" kj-trigger="revealed"></div>
    <span kj-get="/price" kj-trigger="every 5s"></span>

    API

    kj-trigger="event [delay:Nms] | load | revealed | every Ns"

    When to fire: any DOM event (with optional debounce), load (immediately), revealed (when scrolled into view, via IntersectionObserver), or every Ns (polling).

    Reference: kj-trigger →

    Migrating

    Coming from htmxSupported: an event name, delay:, load, revealed, every Ns. Not included: multiple events in one trigger, changed, once, throttle:, from:, intersect options and [filter] expressions.

    Related features