Skip to content

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.

A responsejson
{
  "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 type first, so the component appears as early as possible while it streams. No built-in prop is named type; the text field's kind is inputType.
  • Containers (Stack, Grid, Card, Form, Disclosure) take children: an array of component objects.
  • Data is plain JSON in a prop: options, tabs, items, chart data, comparison features, sources. An item that holds content, such as a tab or an accordion section, has its own children array.
  • 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.

A bound sliderjson
{
  "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 initial written for a name is used.
  • The starting value, in order of precedence: what the person entered, a value restored with initialState, the binding's initial, the control's own literal value or checked, 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.

A computed resultjson
{ "type": "Output", "label": "Yearly cost", "value": { "$expr": "round(seats * 12 * 12, 2)" }, "unit": " USD" }
Expression syntax
PartSupported
ValuesNumbers, "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 down
  • ceil(x) rounds up
  • abs(x) drops the sign
  • sign(x) is -1, 0, or 1
  • sqrt(x) is the square root
  • pow(x, y) is x to the power y
  • min(a, b, ...) is the smallest
  • max(a, b, ...) is the largest
  • sum(a, b, ...) adds the numbers
  • avg(a, b, ...) is the mean
  • clamp(x, low, high) keeps x between low and high