Subform
A subform is a nested form rendered inside a parent form. It has its own title, toolbar, button bar, and fields — and its actions are dispatched directly to the nested object, not to the parent.
Mateu detects subforms automatically from the type of a field. No special annotation is needed on the field itself.
When a nested type becomes a subform
Section titled “When a nested type becomes a subform”A field of a complex type (class or record) is rendered as a subform when its type meets both of these conditions:
- It has at least one method (or field) annotated with
@Buttonor@Toolbar. - It has at least one non-action data field.
If neither condition is met, the nested type is rendered as an embedded field group (the legacy behaviour): the sub-fields appear inline inside the parent form with no chrome around them.
Example
Section titled “Example”// Nested type — qualifies as a subform because it has @Toolbar and data fieldspublic record Subform1(String address, String city) {
@Toolbar Object save() { return Message.builder() .text("Saved: " + this) .build(); }}
// Another nested type — qualifies as a subform because it has @Buttonpublic record Subform2(@Stereotype(FieldStereotype.radio) Sex sex, Religion religion) {
@Button Object confirm() { return Message.builder() .text("Confirmed: " + this) .build(); }}
// Parent form@Route("/page3")public class Page3 {
String name; int age;
@Section("Address") Subform1 subform1; // → rendered as a subform with a toolbar
@Section("Preferences") Subform2 subform2; // → rendered as a subform with a button bar
@Toolbar Object save() { return Message.builder() .text("Saved page: " + this) .build(); }}Page3 renders as a single page that contains:
- Its own toolbar with the
saveaction. - A section titled “Address” containing a subform with
addressandcityfields and asavetoolbar button scoped toSubform1. - A section titled “Preferences” containing a subform with
sexandreligionfields and aconfirmbutton scoped toSubform2.
Action scoping
Section titled “Action scoping”When the user clicks a button inside a subform, the action is dispatched to the nested object, not to the parent. The nested object receives the current values of its own fields and executes the annotated method.
This means the parent form does not need to know about or delegate to the child’s actions. Each subform manages its own behaviour independently.
Toolbar vs buttons
Section titled “Toolbar vs buttons”The same rules that apply to top-level forms apply inside a subform:
| Annotation | Rendered as |
|---|---|
@Toolbar | Button in the subform toolbar (top area) |
@Button | Button in the subform footer (bottom area) |
Embedded field group (no actions)
Section titled “Embedded field group (no actions)”If the nested type has data fields but no @Button or @Toolbar annotations, it is rendered as an embedded field group without any chrome:
public record Address(String street, String city, String zip) {}
public class CustomerForm { String name; Address address; // → fields embedded inline, no subform chrome}Use a subform when the nested object has its own actions. Use an embedded group when it is pure data with no actions.