Skip to content

Interactions

A document can say what a screen looks like. This page is about the other half: what happens when someone clicks something.

screen InvoiceList
on enter
refresh Invoices
table Invoices
column invoiceId
on double click
open dialog InvoiceDetails
with invoiceId from item.invoiceId

Three words carry the whole idea:

WordWhat it is
Interaction triggerWhat starts it — the on <thing> clause.
ActionWhat happens — execute, navigate to, open dialog, refresh, and the rest of a closed set.
BehaviorA bundle of trigger-to-action bindings, attached to something.

For one labeled button that selects a command from the current item’s state, use a guarded screen action. For a gesture that chooses a whole action list, use block-form alternatives below.

Most interaction is one trigger and one action, written where it belongs:

button
on click
execute RegisterInvoice

That inline on block is a behavior — an anonymous one, attached to the button. When the same wiring is wanted in several places, give it a name, declare parameters, and attach it with uses:

behavior ConfirmThenExecute
parameter command
parameter message
on click
confirm message
on success
execute command
module Invoicing
feature InvoiceManagement
slice StateChange CancelInvoice
screen CancelInvoice
uses ConfirmThenExecute
command CancelInvoice
message $strings.confirmCancel

There is one construct behind both forms, so a named behavior and an inline block resolve, print and diagnose identically. The only difference is the name.

An interaction trigger is never declared. It exists only as the on clause of a binding.

TriggerOccurs whenCarries
on clickthe element is activated — clicked, tapped, or confirmed from the keyboardthe element’s data item, if any
on double clickit is activated twicethe element’s data item
on selectselection changes in an items controlthe selected item
on submita form is submitted and passes the modeled validationthe form’s values
on changea bound value changesthe old and new value
on load / on unloadthe element or screen becomes live, or is torn downroute parameters
on enter / on leavethe screen is navigated to, or away fromroute parameters
on event <Event>a modeled domain event is observedthe event’s payload
on interval <n> <unit>a timer elapses—
on <ApplicationTrigger>a declared application trigger firesthat trigger’s declared payload

The last two rows are the bridge to the backend vocabulary, and the reason this page distinguishes two kinds of trigger.

trigger still means one thing. A declared trigger <Name> is an application trigger - a signal with a payload shape that a reaction ... when consumes. See Triggers. What starts an interaction is the anonymous on clause above. The built-in kinds (click, submit, enter, and their siblings) are reserved inside an on clause only; an identifier of the same name anywhere else in a document is just an identifier. Declaring an application trigger named after one of them is an error, because on <Name> would then mean the interaction and never the trigger.

on click, on double click and on select can choose one whole action list:

table Invoices
column status
on double click
when item.status == "draft"
open dialog InvoiceDetails
with invoiceId from item.invoiceId
otherwise
notify info "Only draft invoices can be edited"

Use exactly the guarded action condition grammar: item.<path> compared with a literal, combined with and, or and parentheses. Each when and the optional final otherwise contains a non-empty indented list. At least one when is required. Plain actions and where cannot mix with alternatives. The one-line when … execute … spelling belongs to labeled actions, not interactions.

The first matching branch runs once against the rendered item when the gesture occurs: no refetch first, no re-evaluation while the list runs. No match runs the fallback if present, otherwise nothing. No subject runs nothing, including fallback. Failure, denial or unavailable commands never fall through; commands still enforce their own rules. This differs from a labeled action’s click-time refresh check because the interaction displays no choice beforehand.

A table’s click or double click uses the activated row; select uses the newly selected item. Clearing selection runs nothing. Other elements use the nearest enclosing container’s single item or selected row, with the same sibling precedence as guarded actions. Named behaviors are checked at each uses site. A component’s context binding or selectedItem does not supply a subject in this version; unresolved or equally near data subjects produce PLAY0346.

on submit has no alternatives: item means populated data, not submitted form values. Event, change, interval and application triggers also have no alternatives. The one-line on row-click navigate to … by … stays unchanged.

Opaque where is deprecated only on click, double click and select (PLAY0564). A strict item condition produces a warning and a C# typed repair to one when block with no fallback; repairs require individual review and refuse comment loss. Other guard text produces Information without a repair. Event/change/application where remains opaque and unchanged. TypeScript reports the same diagnostics but does not offer these repairs.

Every action names something the document declares. That is what makes an action that points at nothing a diagnostic rather than a control that quietly does nothing.

ActionEffectResolves against
execute <Command>submits a modeled commandthe document’s commands
navigate to <Screen>changes the active screenthe document’s screens
navigate backpops navigation history—
open dialog <DialogTemplate>opens a dialog over the applicationthe module’s dialog templates
close dialogdismisses the innermost dialog—
refresh <Query>re-runs a query-backed elementthe document’s queries
set <target> to <value>writes screen statethe screen’s declared state
notify <info|warning|error> "<text>"surfaces a message$strings. accepted
confirm "<text>"gates the actions that follow on the user agreeing—
raise <ApplicationTrigger>fires a declared application triggerthe document’s triggers

Arguments are passed with with <name> from <binding>:

on click
open dialog InvoiceDetails
with invoiceId from item.invoiceId

A message operand may be a literal, a $strings. key, or the name of a parameter the attachment supplies — which is how one named behavior serves several call sites.

An action that has an outcome can branch on it:

on click
execute RegisterInvoice
on success
close dialog
refresh InvoiceList
notify info $strings.invoiceRegistered
on failure
notify error $strings.registerFailed

Actions run in the order they are written. confirm short-circuits everything after it when the user declines.

ContinuationValid on
on success, on failureexecute, refresh, confirm, open dialog, raise — the actions that can fail
on resultopen dialog only

Attaching on success to a navigate, a notify, a set or a close dialog is an error. Those have no outcome to branch on, so the continuation could never run — and an author who wrote one expected it to.

Writing actions after an unconditional navigate is reported too: the screen they were written for is already gone.

After a command succeeds, you can pass its response to the next action by name. A scalar returns invoiceId exposes invoiceId; a returns block exposes its field names. Those names are available in the form’s one-line or block on submit, and in on success beneath execute <Command>.

A bare value in with … from …, set … to …, or an unquoted notify / confirm message resolves to the nearest successful command response that supplies that name. A form field or behavior parameter with the same name keeps its existing meaning. Dotted paths, $-rooted bindings, quoted literals and other opaque text are unchanged. Action declaration names and state-write targets are not response operands.

A nested command’s on success shadows matching outer response names only inside that subtree; siblings restore the outer scope. Its on failure cannot use its returned names (PLAY0685), but can still use an outer command’s successful response. Continuations of refresh, confirm, open dialog and raise inherit their surrounding value scope rather than creating a response. A parameterized execute command inside a named behavior has no known response shape at declaration time.

Monaco and VS Code complete returned names after from and the form sugar’s by, and hover shows the field type. This is an authoring contract outside the ESM: renderers must bind response values. Screen action <Command> navigation sugar does not gain a response scope.

A behavior attaches at every level of the containment tree, and means the same thing at each:

LevelReaches
layoutevery screen in the application
moduleevery screen in the module
featureevery screen in the feature, including nested ones
screen template, dialog templateevery screen that uses it
formthat form
screen, section, slot, tablethat element and what is inside it
module Invoicing
uses ConfirmDestructive
screen template Workspace
fits slot main
content
on enter
refresh Invoices

In a form body, on submit navigate to <Screen> keeps its existing one-line meaning. Any other on is a behavior attached to the form.

Attachments are additive. A behavior on a template and a behavior on an element both run — the more specific one does not replace the less specific one. They run outermost first, unless a behavior declares its own order:

behavior ConfirmDestructive
order 10
...

This is the same outward-resolving rule contributions already use.

In a folder of files, a module’s or a feature’s attachments may be written in any file that names it, and they accumulate the same way. An attachment identical to one another file already makes is ignored with a warning rather than run twice; expansion writes them back into the owner’s own file.

  • Every operand resolves, or is reported. Unknown commands, screens, queries, dialog templates, events, triggers and behaviors are warnings, the way every other reference in the document is — a name may still resolve to something outside it, and what matters is that the gap stays visible.
  • A uses site must supply exactly the parameters the behavior declares. A missing or unknown argument is an error.
  • Continuations must sit on actions that can fail, and on result only on open dialog.
  • Nesting is capped. Continuations nest arbitrarily in principle; past sixteen levels the compiler asks for a named behavior instead of reporting a stack overflow.

The full list, with codes, is in Diagnostics.

Interaction is a closed vocabulary, deliberately. It is not a scripting language: conditions use the existing expression grammar, and actions are a fixed, resolvable set. Validation stays modeled on the command — the UI surfaces it rather than restating it. Animation, visual states, drag-and-drop and keyboard maps are not part of it.

The event model board draws the containing table or screen as a static prototype; it does not execute gesture action lists or invent a button for each branch. Runtime support belongs to renderers, which must reject alternatives they cannot realize.

Interaction constructs are deferred from the backend ESM v1 profile, the same way screen, layout and ui profile are, and report PLAY0269. Deferred does not mean dropped: the syntax tree, the printer and the semantic model carry every construct in full, and a target that cannot realize one reports it.