Forms
A form accepts one quoted or fenced-text description body line. It prints first before populate and fields, as report-only authoring metadata rather than a localizable UI label. Individual fields do not gain descriptions. Markdown documentation is not supported here.
A screen’s action directive exposes a command, but says nothing about how a user enters the data that command needs. Naming every field on the screen that invokes it would tie one input surface to one place it can be invoked from - a form is that input surface, declared once and reused wherever its command is.
populate via query ... by value and on submit navigate ... by value remain scalar lookups. They cannot supply a composite read-model key. A single-instance composite lookup reports PLAY0661; no automatic form repair is offered. See Read-model keys.
Syntax
Section titled “Syntax”form <Name> for <Command> populate via query <Query> [by <param>] # -- or -- populate from item
generation auto|manual
field <property> [from <source>|compose using <Callback>] [label "<text>"] ...
columns auto # -- or -- columns manual column <property> [label "<text>"]
layout column <index> [width <width>] [min <width>] [max <width>] columnGap <width> rowGap <width> place <field> row <row> column <column> [rowSpan <n>] [columnSpan <n>] [width <width>]
on submit navigate to <Screen> [by <param>]form <Name> for <Command>- top level, alongsidescreen templateandfeature, inside amodule. A module can declare more than one; each name must be unique.populate- where the form’s initial values come from. At most one per form, and optional - a form with nopopulatestarts empty.generation- preserves Scene’sFormGenerationMode:automeans generate fields from command metadata, whilemanualmeans the authored fields are the contract. At most one per form.field- binds one of the command’s properties to the form. Zero or more.columns- which command properties the form shows, in which order and with which labels.columns autolets the package infer them, whilecolumns manuallists the command properties in display order. At most one per form.layout- Scene 4.12 command-form geometry: authored columns, field placements and gaps. It is independent ofgenerationand does not reinterpretcompose usingas layout metadata.on submit- what happens after a successful submit. At most one per form, and optional - a form with noon submitstays on the current screen.
Example
Section titled “Example”form RegisterInvoiceForm for RegisterInvoice populate via query GetInvoiceDraft by invoiceId
field customerName field dueDate label "Due date" field totalAmount from calculatedTotal field lineItems compose using BuildLineItems
generation manual columns manual column customerName label "Customer" column dueDate label "Due date" column totalAmount
layout column 1 width 1fr min 240px max 50% column 2 width 2fr columnGap 24px rowGap 16px place customerName row 1 column 1 width auto place dueDate row 1 column 2 width 100% place totalAmount row 2 column 1 columnSpan 2
on submit navigate to InvoiceList by invoiceIdA form is discovered, not referenced
Section titled “A form is discovered, not referenced”A form never appears in a screen’s directive tree the way a table or summary does. It is discovered by its for <Command> binding wherever that command is invoked - an action RegisterInvoice on any screen renders RegisterInvoiceForm as its input surface automatically, the same way a ui profile is discovered by a build rather than named on a screen. This keeps one command’s input surface in one place, however many screens invoke it.
Populating a form
Section titled “Populating a form”populate via query <Query> [by <param>]- seeds the form’s initial values from a query result, the same shape a screen’sdatadirective uses.populate from item- reuses an item already bound in scope, such as the row a table’son row-clicknavigated from. No new binding mechanism - it resolves the same way every other bare name in the document does (see How a name resolves).
Fields
Section titled “Fields”A bare field <property> binds straight to the command property of the same name. Three optional refinements adjust that:
| Form | Meaning |
|---|---|
field <property> label "<text>" | Overrides the display label. |
field <property> from <source> | Binds from a differently-named source property. |
field <property> compose using <Callback> | Computes the value from a callback instead of binding it directly. |
from and compose using are mutually exclusive on one field; either may still carry a label. A field’s label accepts an unquoted $strings.<key> token in place of a literal, the same as everywhere else in the document - see Internationalization.
Columns and layout
Section titled “Columns and layout”columns and layout answer different questions and can be used together: columns decides the order and labels of the fields, and layout decides their geometry. Neither replaces the other.
Use columns auto when the native command form runtime should derive the display order from the command and package defaults. Use columns manual when the business order matters or the screen needs deterministic columns across packages.
form RegisterInvoiceForm for RegisterInvoice field customerName field dueDate field totalAmount
columns manual column customerName label "Customer" column dueDate label "Due date" column totalAmountA manual column names a command property already present in the form. The compiler preserves the column order and labels for renderers; a renderer that cannot honor manual columns must diagnose that mismatch instead of silently reverting to an automatic layout.
layout carries the richer Scene 4.12 geometry contract. Widths use the same units as Scene: auto, fractional units such as 1fr, pixels such as 240px, and percentages such as 50%. Column indexes, rows and columns are one-based for authored stability. rowSpan and columnSpan are optional positive integers.
form RegisterInvoiceForm for RegisterInvoice generation manual field customerName field dueDate field totalAmount
layout column 1 width 1fr min 240px max 50% column 2 width 2fr columnGap 24px rowGap 16px place customerName row 1 column 1 width auto place dueDate row 1 column 2 width 100% place totalAmount row 2 column 1 columnSpan 2compose using remains a value-composition hook only. Do not use it to smuggle width, row or placement metadata; renderers consume layout for geometry.
Submitting
Section titled “Submitting”on submit navigate to <Screen> [by <param>] runs after the form’s command succeeds. A bare by name can use a scalar response’s source name or a field name from a returns block. For example, RegisterInvoice in Invoicing declares returns invoiceId, and its form uses on submit navigate to InvoiceDetails by invoiceId without supplying an invoice identity field.
A block on submit has the same response scope: use with id from invoiceId on a navigation action, or a returned name in set … to, notify or confirm. Form fields keep their existing meaning if their names collide with response names. Nested execute success and failure branches follow the continuation value rules. Omit on submit and a successful submit simply leaves the user where they were.
This form scope is not added to a screen’s action … navigate … by … sugar. Compilers resolve the names; renderers must bind the actual values.
How references resolve
Section titled “How references resolve”for <Command>, populate via query <Query>, and on submit navigate to <Screen> all resolve the same inside-out way every other bare name in the document does (see How a name resolves) - unresolved and ambiguous references are warnings, not errors, because a name may still resolve to something outside the document. A form sits at module level rather than inside a slice, so it disambiguates by module but not by feature or slice, since it does not sit inside either.