Quick start
Bring Jackson yourself (io.quarkus:quarkus-jackson in a Quarkus app) — pha ships only
the annotations, so applications that never touch JSON pay nothing. Then bind and render:
String payload = """
{"id": "mn-api", "items": [{"text": "Edit"}, {"text": "Delete", "danger": true}]}
""";
Menu menu = objectMapper.readValue(payload, Menu.class);
String html = engine.getTemplate("pha:components/navigation/menu").data("menu", menu).render();
Validation carried by the builders still applies on the JSON path — an empty menu, a badge without a
value, or a payload that does not bind is rejected at
readValue time, never half-rendered.
Shape rules
JSON property names are the model's field names — the same names the Java builders use, so the Java tab on any example doubles as the property reference. The rules, by model shape:
-
Builder models (Menu, Table, Card, …): properties bind to the builder — booleans for the toggles
(
{"plain": true}), arrays for item lists ({"items": [...]}), nested objects for composed models. Java-only conveniences that set several fields at once (searchFilter(a, b)) are covered by their field-level properties (searchPlaceholder,searchAriaLabel). -
Immutable item models (MenuItem, TabItem, Label, …): the fields bind directly —
{"text": "Delete", "danger": true}is the JSON form ofMenuItem.of("Delete").asDanger(). - Internal state is not contract: computed wiring (Alpine state, resolved tree rows) has no JSON property. The contract is enforced by a sweep test over all 100 models — a model that gains a field without a JSON binding fails the build.
The pha: template URI
pha:components/navigation/menu is a stable, descriptor-friendly address for a Qute
template. The scheme resolves from any classpath templates/ root — pha's own
components, your application's fragments, and template jars published by API services — so a descriptor can name any
fragment without knowing which artifact ships it. Traversal outside templates/ does not
resolve.
Try it
This showcase exposes the flow as an endpoint (POST /api/pha/render?template=…&model=…
— showcase-only, not part of the extension). Edit the payload and render: the HTML below comes entirely from the
server.