Fragments
Server-driven UI without writing JavaScript: any route can return a fragment (an Element without html/body), and declarative swap attributes fetch it and insert it into the page. Swaps are wrapped in a View Transition when the browser supports it.
Basic Swap
The server route returns a fragment; any element triggers the swap on click.
// Server: returns just the list markup
app.get("/products/list", req ->
productList(PAGE.from(req)));
// Client: zero JavaScript written
button(attrs().swap("/products/list?page=2", "#products"),
text("Next page"))Progressive Forms
swapForm posts the form data and swaps the returned fragment into the target. With the action attribute set, the form still works when JavaScript is disabled — it just submits natively to the same route.
form(attrs().action("/contact/submit").method("post") // no-JS fallback
.swapForm("/contact/submit", "#form-status"), // progressive swap
field("Name", "name", "text", "Your name"),
div(attrs().id("form-status")),
submitButton("Send"))
// The route returns a status fragment:
app.post("/contact/submit", ctx -> {
messageStore.save(ctx.formParam("name"), ...);
return ContactStatus.success("Message sent!");
});Morphing
swapMorph updates the target in place instead of replacing its HTML: unchanged nodes are kept, so focus, scroll position, and in-progress input survive. Ideal for lists or dashboards that refresh while the user is interacting.
div(attrs().id("inbox").swapMorph("/inbox/refresh", "#inbox"))History & Events
- swapPush(url) adds a browser-history entry; back/forward re-swap naturally
- swapOuter(url, sel) replaces the target element itself (outerHTML)
- a jweb:swap CustomEvent fires on document after every swap