Hypermedia, the short version
Instead of fetching JSON and rendering it in JavaScript, let the server send back the finished HTML fragment. jsKjs reads five attributes — kj-get, kj-post, kj-put, kj-patch, kj-delete — and wires the request for you.
Smart defaults keep markup short: forms fire on submit and send their fields; inputs and selects fire on change and send their own name=value; everything else fires on click. Bodies are URL-encoded (forms with file inputs switch to multipart automatically), exactly what PHP, Rails, Django and Laravel expect.
Content added later — including content returned by the server, like the delete buttons in the notes below — is wired automatically by a MutationObserver.
- The server renders each note — and its delete button.
Endpoint: /api/notes.php renders the note with PHP and escapes it. Nothing is stored — refresh and the list resets.
View the source for this demo
<form id="note-form" class="row" kj-post="/api/notes.php" kj-target="#notes" kj-swap="afterbegin">
<label for="note-text" class="sr-only">New note</label>
<input id="note-text" name="text" type="text" maxlength="140" required placeholder="Write a note, press Enter" style="flex:1;min-width:200px">
<button type="submit">Add note</button>
</form>
<ul id="notes" class="list-plain" aria-live="polite" style="margin-top:12px">
<li id="note-seed" class="note-row"><span>The server renders each note — and its delete button.</span>
<button type="button" kj-delete="/api/notes.php?id=seed" kj-target="#note-seed" kj-swap="outerHTML" aria-label="Delete note">✕</button></li>
</ul>// The only JavaScript on this demo: clear the field once the server has answered
$('#note-form').on('kj:after', function () { this.reset(); });The server's view of your request appears here.
View the source for this demo
<div class="row">
<button type="button" kj-get="/api/echo.php" kj-vals='{"q":"jskjs"}' kj-target="#verbs-out">kj-get</button>
<button type="button" kj-post="/api/echo.php" kj-vals='{"title":"New"}' kj-target="#verbs-out">kj-post</button>
<button type="button" kj-put="/api/echo.php" kj-vals='{"title":"Replaced"}' kj-target="#verbs-out">kj-put</button>
<button type="button" kj-patch="/api/echo.php" kj-vals='{"title":"Tweaked"}' kj-target="#verbs-out">kj-patch</button>
<button type="button" kj-delete="/api/echo.php?id=7" kj-target="#verbs-out">kj-delete</button>
</div>
<div id="verbs-out" aria-live="polite" style="margin-top:12px"><p class="muted">The server's view of your request appears here.</p></div>More examples
Typical hypermedia markup
<!-- Load a partial when clicked -->
<button kj-get="/partials/stats.html" kj-target="#stats">Refresh stats</button>
<!-- Inline edit: PATCH and replace the row with the server's version -->
<form kj-patch="/api/rows/7" kj-target="#row-7" kj-swap="outerHTML">…</form>
<!-- A checkbox that saves itself on change -->
<input type="checkbox" name="done" kj-put="/api/tasks/3/done">What the server returns (PHP)
<?php // /api/notes.php — return HTML, escape everything
$text = htmlspecialchars($_POST['text'] ?? '', ENT_QUOTES, 'UTF-8');
$id = bin2hex(random_bytes(4));
echo "<li id='note-$id'>$text <button kj-delete='/api/notes.php?id=$id'
kj-target='#note-$id' kj-swap='outerHTML'>✕</button></li>";API
The URL to request with that HTTP verb. Forms send their fields; named inputs, selects and textareas send their own value. Bodies are URL-encoded (multipart when a form contains file inputs). Default triggers: forms → submit, inputs/selects → change, everything else → click.
Where the response HTML goes (default: the element itself).
Reference: kj-target →Migrating
hx- to kj-: hx-get → kj-get, hx-target → kj-target. Requests carry X-Requested-With: jsKjs instead of htmx's HX-Request header.Related features
Targets & swaps
Control exactly where server HTML lands with kj-target and how it is inserted with kj-swap — innerHTML, outerHTML, beforebegin, afterbegin, beforeend, afterend, textContent or none..
Open →HyperTriggers & debouncing
Choose when requests fire with kj-trigger: any DOM event plus a debounce like keyup delay:400ms — live search and server-side inline validation without JavaScript..
Open →CoreForms & serialization
Turn forms into query strings or plain objects with serialize() and serializeObject(), then submit them with $.post — or declaratively with kj-post..
Open →