ReferenceComponents

form

Typed fields and one submit, returned as a form submission.

Structured input, when a sentence will not do. form collects between one and ten typed fields and sends them back as a form_submission, a separate request shape from a message, so the values arrive typed rather than parsed back out of prose.

Six field kinds cover what a copilot actually needs: text, number, select, toggle, date and time.

Start a return
Order number1042
ReasonChoose one
Pickup dateOptionalPick a day
Send

Re-rendered in the browser from the SDK’s own scales. Not a screenshot.

Rendered natively by the Flutter SDK and the Web SDK, both since 0.1.0.

When not to use it

Do not use a form for one question with a handful of answers. That is quick_replies, and it is one tap instead of two.

Do not use a time field to book an appointment: the user cannot know which times are free, so offer datetime_slots instead. And do not use a form to approve something: approval is confirm_card, built server-side.

Props

Every block also carries type and a required fallback_text of 1 to 300 characters.

PropTypeRequiredConstraints
form_idstringYes1 to 64 characters. Comes back with the submission.
titlestringNoUp to 120 characters.
fieldsarrayYes1 to 10 entries.
fields[].kindtext | number | select | toggle | date | timeYesNone
fields[].namestringYes1 to 64 characters. The key in values.
fields[].labelstringYes1 to 80 characters.
fields[].requiredbooleanNoFor a toggle this means "must be on".
fields[].placeholderstringNoUp to 80 characters. Never used as the label.
fields[].valuestring | number | booleanNoPrefill. Never overwrites an answer the user has already typed.
fields[].optionsarrayNoUp to 20 entries. A select needs them, but the schema does not enforce it; one that arrives without them renders inert.
fields[].options[].labelstringYesUp to 60 characters.
fields[].options[].valuestringYesUp to 60 characters.
submit_labelstringYes1 to 40 characters.

On the wire

{
  "id": "blk_11",
  "type": "form",
  "fallback_text": "A return request form: order number, reason, number of pairs.",
  "props": {
    "form_id": "return_request",
    "title": "Start a return",
    "fields": [
      {
        "kind": "text",
        "name": "order_id",
        "label": "Order number",
        "required": true,
        "placeholder": "1042"
      },
      {
        "kind": "select",
        "name": "reason",
        "label": "Reason",
        "required": true,
        "options": [
          {
            "label": "Too small",
            "value": "too_small"
          },
          {
            "label": "Arrived damaged",
            "value": "damaged"
          }
        ]
      },
      {
        "kind": "number",
        "name": "pairs",
        "label": "How many pairs",
        "required": true
      },
      {
        "kind": "toggle",
        "name": "keep_box",
        "label": "I still have the original box"
      }
    ],
    "submit_label": "Request pickup"
  }
}

Interaction

Submit validates, then posts:

{
  "form_submission": {
    "form_id": "return_request",
    "values": { "order_id": "1042", "reason": "damaged", "pairs": 2, "keep_box": true }
  }
}

Values are string, number or boolean. A number field is parsed and sent as a number; date sends YYYY-MM-DD and time sends HH:mm, both as strings.

Half-filled values are stored on the block itself, so scrolling a form out of the thread and back does not empty it.

States

StateWhat the SDK draws
InvalidErrors appear under the field they belong to, never as a summary at the top of a form the user has already scrolled past. The field's border goes to full ink at 1.5pt. A count ("3 fields above need fixing.") sits directly above the Send button, because in a long form the per-field messages are above the fold the button is in and pressing Send otherwise looked like nothing happened.
Select with no optionsThe control renders and announces itself as not chosen, but is inert: it does not open a sheet onto an empty list.
EmptyA fields array with no entries, or no field carrying a name, renders fallback_text inside the plate.
SendingThe button reads "Sending…" and every field is disabled.
SentThe form is replaced by a read-only list of what was submitted, plus a "Sent" mark. The swap is sized and crossfaded rather than jumping.
Turn still openIf the assistant is mid-answer the submission is refused and the form says so, rather than reporting "Sent" for a request that never left.
LoadingA plate with a title bar, two field boxes at 44pt and a button shape.

Accessibility

Text and number fields merge their visible label into the field's own accessible node, so they are still named once the placeholder is gone. Select, date, time and toggle carry a complete label of their own and the visible one is hidden from the reader to avoid saying it twice. Errors are live regions.

fallback_text should describe what the form asks for, so a user who cannot see it knows whether it is worth filling in.