Model vs Pages
Most Mateu UIs should be defined at the model level. Explicit routed pages (a class bound to a URL by a routes.yaml entry) are the exception, not the default.
The principle
Section titled “The principle”When the framework can infer what the user needs from the model, there is no reason to write a separate page class. The model becomes the single source of truth for both data and UI.
Example: relationships in the model
Section titled “Example: relationships in the model”Instead of creating a separate editor page for a related entity:
// Not recommended for standard relationshipspublic class ProductCategoryEditor { // lots of wiring...}type: Routesroutes: - route: products/:id/categories/edit viewModel: com.example.ProductCategoryEditorExpress the relationship directly in the model with an annotation:
@Lookup(search = CategoryOptionsSupplier.class, label = CategoryLabelSupplier.class)List<String> categories;From this single annotation, Mateu generates:
- a selection UI (dropdown, checkbox list, or similar)
- label resolution from IDs
- integration with the CRUD form
- persistence integration
Example: status field
Section titled “Example: status field”Instead of a custom page to change a product’s status:
@Stereotype(FieldStereotype.radio)ProductStatus status;Mateu renders the radio group in the existing form. No extra page needed.
When NOT to create a page
Section titled “When NOT to create a page”You usually do not need a routed page when:
- you are doing standard CRUD
- the UI can be inferred from the model type and annotations
- relationships can be expressed with
@Lookupor similar - actions fit naturally as
@Buttonor@Toolbarmethods on the existing form
When to create a page
Section titled “When to create a page”Create an explicit page class (and give it a routes.yaml entry) when:
- you need a multi-step wizard or guided flow
- you need a custom dashboard or composite layout
- the behavior genuinely cannot be expressed declaratively
- you need a standalone URL for deep linking or sharing
public class ProductOnboardingWizard {
// Step 1 String name; ProductStatus status;
// Step 2 String description; int initialStock;
// Step 3 List<String> categories;
@Button @Action(validationRequired = true) public URI finish() { String id = productRepository.create(name, status, description, initialStock, categories); return URI.create("/products/" + id); }
}type: Routesroutes: - route: products/onboarding viewModel: com.example.ProductOnboardingWizardMental model
Section titled “Mental model”- model = source of truth for data and default UI
- annotations = UI intent and behavior
- routed page (
routes.yamlentry) = escape hatch for custom flows
Push as much as possible into the model. Reach for explicit pages only when the model cannot express the required behavior.