GenUI
Response format
A response is one JSON object: components with their props inline, containers with children, and shared values a model can bind and compute.
Components are objects
Every component is an object with a type naming a catalog component and its props inline. The response is one component, normally a Stack. This form submits Plan: Pro, $12 per seat; Seats: 5 as the person's next message.
{
"type": "Stack",
"children": [
{ "type": "Heading", "text": "Choose a plan", "level": 2 },
{
"type": "Form",
"name": "plan",
"title": "Choose a plan",
"submitLabel": "Continue",
"children": [
{
"type": "RadioGroup",
"label": "Plan",
"name": "plan",
"options": [
{ "value": "free", "label": "Free" },
{ "value": "pro", "label": "Pro, $12 per seat" }
],
"value": "pro"
},
{ "type": "NumberField", "label": "Seats", "name": "seats", "value": 5, "min": 1, "max": 100 }
]
}
]
}- Write
typefirst, so the component appears as early as possible while it streams. No built-in prop is namedtype; the text field's kind isinputType. - Containers (
Stack,Grid,Card,Form,Disclosure) takechildren: an array of component objects. - Data is plain JSON in a prop:
options,tabs,items, chartdata, comparisonfeatures,sources. An item that holds content, such as a tab or an accordion section, has its ownchildrenarray. - Optional props may be left out or written as
null. - Required props must be present. A component that lacks one is not drawn, and it is reported once the response is complete.
- A response may arrive inside a Markdown code fence; the parser skips the fence.
Share a value with $bind
A control that other parts depend on reads and writes a shared name. Write { "$bind": "seats" } as its value (or checked), optionally with a starting value.
TextField, TextArea, NumberField, Select, RadioGroup, CheckboxGroup, Slider, and DatePicker bind value; Checkbox and Switch bind checked.
{
"type": "Slider",
"label": "Seats",
"name": "seats",
"min": 1,
"max": 50,
"value": { "$bind": "seats", "initial": 5 }
}- Names are letters, digits, and underscores, do not start with a digit, and are at most 64 characters.
- The same name in several controls shares one value. The first
initialwritten for a name is used. - The starting value, in order of precedence: what the person entered, a value restored with
initialState, the binding'sinitial, the control's own literalvalueorchecked, then the control's default (the slider's minimum, an empty text field, unchecked). - A control that is not bound stores its value under
<form name>.<name>, or just<name>outside a form. A bound control stores under its binding name wherever it is. - Defaults are written into the state once the response is complete. That is not an edit and does not call
onStateChange, but the state you save and a form submit always include them.
Compute a value with $expr
A computed value is { "$expr": "seats * 12" }, evaluated against the bound names and evaluated again whenever one of them changes. Output.value, Meter.value, ProgressBar.value, and Text.text accept expressions.
+ adds numbers or joins text. There is no property access, no assignment, and no call outside the list below; a hand-written parser reads the source and the evaluator walks the tree. A name with no value reads as null, arithmetic with null is null, and a result that is not a finite number, such as a division by zero, is null and shows as empty.
Expressions are limited to 500 characters and 200 nodes. One that does not parse is an error, and one that reads a name no control binds is reported with the fix.
{ "type": "Output", "label": "Yearly cost", "value": { "$expr": "round(seats * 12 * 12, 2)" }, "unit": " USD" }| Part | Supported |
|---|---|
Values | Numbers, "text" or 'text' literals, true, false, null, and bound names. |
Operators | + - * / %, < <= > >=, == !=, && || !, unary -, a ? b : c, and parentheses. |
round(x, digits?)rounds to a number of decimals (0 to 10; 0 by default)floor(x)rounds downceil(x)rounds upabs(x)drops the signsign(x)is -1, 0, or 1sqrt(x)is the square rootpow(x, y)is x to the power ymin(a, b, ...)is the smallestmax(a, b, ...)is the largestsum(a, b, ...)adds the numbersavg(a, b, ...)is the meanclamp(x, low, high)keeps x between low and high