The question component (canvas.question) shows a multiple-choice question card during a conversation. The user taps an option (or types a free-text answer, when allowed) and the structured result goes to the PAL and your conversation webhook.
Triggering
The PAL shows the card by invoking canvas_show_question when the user should answer a structured question. Steer the timing in the PAL’s system prompt.
- Renders in the
safe-area-right slot by default.
- A repeat
canvas_show_question invocation replaces the card currently on screen with a new instance and a new tool_call_id; in-progress input is lost and interactions arrive under the new id.
- Only one canvas card is on screen at a time. A new
canvas_show_question invocation always replaces the current card, even when it targets a different side slot (e.g. layout.preferred_slot: "safe-area-left"); two cards never appear at once.
- Every Canvas PAL also gets an
update_component control action that patches the existing card without replacing it.
question has no component-specific settings beyond enabled. See Enabling and configuring components.
Arguments
The PAL passes these to canvas_show_question:
| Field | Type | Required | Description |
|---|
question | string | ✅ | The question text shown on the card. Must be non-empty. |
options | array | ✅ | The selectable options. 2–10 items. Option ids must be unique. |
options[].id | string | ✅ | Stable identifier, echoed back verbatim in selected_option_ids. |
options[].label | string | ✅ | Human-readable option text shown to the user. |
options[].allow_free_text | boolean | ❌ | When true, selecting this option reveals a text input for extra detail on that specific choice. Independent of allow_other. |
options[].free_text_placeholder | string | ❌ | Placeholder for that option’s detail input. |
allow_skip | boolean | ❌ | Whether the user may skip the question. Default false. |
multi_select | boolean | ❌ | Whether the user may select more than one option. Default false. |
allow_other | boolean | ❌ | Adds an “Other” affordance the user can expand to type a free-text answer. Single-select: mutually exclusive with the preset options. Multi-select: submitted alongside them. Default false. |
other_label | string | ❌ | Label for the “Other” affordance (e.g. “None of the above”). A generic label is used when omitted. |
other_placeholder | string | ❌ | Placeholder for the “Other” text input. |
correct_answer | string | ❌ | Option id of the correct answer for quiz-style questions. When set, the card briefly reveals the correct option against the user’s choice after they submit, then clears. Must match one of options[].id. Omit for surveys, preferences, and open-ended questions; use only when there is one objectively correct option. |
Tavus injects two runtime controls on every Canvas action:
| Field | Type | Values |
|---|
layout.preferred_slot | enum | safe-area-right, safe-area-left |
display_mode | enum | inline |
Example invocation
Quiz example with answer reveal:
Interactions
question is a submit-capable component emitting six interaction types: submit, skip, dismiss, clear, error, and heartbeat. Only submit and skip carry an answer value; the rest are lifecycle signals with no question payload. Each interaction reaches your conversation webhook as a canvas.interaction event with the answer in properties.value.
Value Shape (submit and skip)
| Field | Type | Required | Description |
|---|
selected_option_ids | string[] | ✅ | The chosen option ids, echoed exactly as the PAL set them. Empty on skip, or when the user answered only via “Other”. |
skipped | boolean | ✅ | true only on skip. |
custom_text | string | ❌ | The free-text answer from the “Other” affordance. Non-empty, at most 4000 characters. |
option_texts | object | ❌ | Per-option detail text, keyed by option id. Keys must be a subset of selected_option_ids. Each value is non-empty, at most 2000 characters. |
Tavus rejects values with any other keys and enforces these rules before delivery:
skip: always skipped: true, with no selections, custom_text, or option_texts.
submit: always skipped: false, with at least one selected id or a non-empty custom_text; never an empty answer.
The hosted card offers Skip only when allow_skip is true. Tavus does not re-check an incoming skip against the original action arguments, so a custom renderer could post one regardless.
Example Payload
properties always carries the same nine keys. interaction_id uniquely identifies the interaction; tool_call_id ties it to the originating canvas_show_question call. created_at is a naive ISO-8601 timestamp with microseconds and no timezone suffix (no trailing Z); treat it as UTC.
Webhook Handling
Branch on type first, then read the value:
Key your logic on option ids, not labels. Labels may vary between conversations; ids are required to be unique and are echoed back verbatim.