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.

QueryParam<Integer> PAGE = QueryParam.of("page", Integer.class).orElse(1);
Element productList(int page) { return div("Page " + page); }

// Server: returns just the list markup
app.get("/products/list", req ->
    productList(PAGE.from(req)));

// Client: zero JavaScript written
button(swap("/products/list?page=2", "#products"),
    "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(action("/contact/submit"), method("post"),          // no-JS fallback
        swapForm("/contact/submit", "#form-status"),         // progressive swap
    label(for_("name"), "Name"),
    input(type("text"), id("name"), name("name"), placeholder("Your name"), required()),
    div(id("form-status")),
    button(type("submit"), "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(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
Fragment responses are served clean — the framework skips script injection for Elements without a <body>, so swapped content never duplicates the runtime.