> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tavus.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Component: question

> Show a multiple-choice question card on screen and get a structured answer back.

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](/sections/conversational-video-interface/magic-canvas/components#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

```json theme={null}
{
  "question": "Which database should we use for the new service?",
  "options": [
    { "id": "postgres", "label": "PostgreSQL" },
    { "id": "mysql", "label": "MySQL" },
    { "id": "sqlite", "label": "SQLite" }
  ],
  "allow_other": true,
  "other_label": "None of the above",
  "other_placeholder": "Tell us what you'd prefer"
}
```

Quiz example with answer reveal:

```json theme={null}
{
  "question": "Which planet is closest to the Sun?",
  "options": [
    { "id": "mercury", "label": "Mercury" },
    { "id": "venus", "label": "Venus" },
    { "id": "earth", "label": "Earth" }
  ],
  "correct_answer": "mercury"
}
```

## 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.

<Note>
  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.
</Note>

### 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.

```json Submit [expandable] theme={null}
{
  "event_type": "canvas.interaction",
  "properties": {
    "conversation_id": "c123456",
    "interaction_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
    "tool_call_id": "toolu_01A0B1C2D3E4F5G6H7J8K9L0",
    "component": "canvas.question",
    "component_version": "v1",
    "type": "submit",
    "value": {
      "selected_option_ids": ["postgres"],
      "skipped": false
    },
    "metadata": {},
    "created_at": "2026-06-09T21:14:03.123456"
  }
}
```

### Webhook Handling

Branch on `type` first, then read the value:

```ts theme={null}
app.post("/webhook", (req, res) => {
  const { event_type, properties } = req.body;
  if (event_type !== "canvas.interaction") return res.sendStatus(200);
  if (properties.component !== "canvas.question") return res.sendStatus(200);

  const { type, value } = properties;
  switch (type) {
    case "submit":
      // value.selected_option_ids: option ids the PAL defined
      // value.custom_text:        "Other" answer, if any
      // value.option_texts:       per-option detail, if any
      saveAnswer(properties.conversation_id, value);
      break;
    case "skip":
      // The user declined to answer.
      break;
    default:
      // dismiss / clear / error / heartbeat: lifecycle only, no answer payload.
      break;
  }
  res.sendStatus(200);
});
```

<Warning>
  Key your logic on option **ids**, not labels. Labels may vary between conversations; ids are required to be unique and are echoed back verbatim.
</Warning>
