Skip to content
hightouchUI

Design system

8624557

Ask user card

Open in Figma

Ask user card is the surface an agent renders in chat when it needs answers before it can continue: it steps the user through single-choice, multiple-choice, and freeform questions, one per card, and submits them together as plain data keyed by question.

Example

Usage

Questions

Each question becomes the card's title while it's active, with previous and next chevrons around a step counter for paging back through answers. A single-choice question advances on its own shortly after a pointer selection, so the choice visibly registers first. Turn it off per question with autoAdvance: false. The last question swaps Next for the submit action, which calls onSubmit with every answer. Pass onSkipAll to offer a way out of the whole flow.

Custom questions

Set allowCustom: true when the options may not cover every answer, and a text area under the choices takes the user's own words. Leave out options for an open-ended question, and the text area renders alone.

Images

Give an option an image to show a thumbnail beside its label, such as the mockup or direction it refers to. Set alt when the image says something the label doesn't; without one it's treated as decorative. A broken image keeps its empty fill.

Hovering the thumbnail opens a larger media preview with the option's label and description. It opens above the thumbnail by default. Set previewPlacement to prefer another side. Set previewWidth for a larger preview, such as for an ad mockup. Pass previewSrc for a sharper source of the same shape, or hasPreview: false for thumbnails that read fine at their small size. A broken image opens no preview.

Details

Set allowDetail: true on an option that needs a word more than its label, and a text input appears under it while it's selected. The text lands in the answer's details, keyed by option value, and only while the option stays selected: deselecting it keeps the text ready for a reselect without handing it back. A pointer pick of such an option doesn't auto-advance, since the field appears with the pick.

On a multiple-choice question, allowDetail and allowCustom both give a text field: use detail for text about one option, and custom for a note about the question as a whole.

Optional questions

By default Next and Submit wait for an answer. Set isRequired: false on a question the agent can proceed without, so the user can move past it unanswered.

Answers

Answers are { selected: string[], custom?: string, details?: Record<string, string> } per question id, where selected holds the chosen option values, custom a typed answer, and details the text typed under selected options. The card is always controlled: keep them in state with answers and onAnswersChange, so an agent that resumes a conversation can re-render the card with what the user already picked. The step is the card's own unless you pass both step and onStepChange.

Custom answers

A typed answer arrives as custom and counts as an answer for Next and Submit. Blank text never reaches answers.

  • On a single-choice question, the typed answer and the options are exclusive. Typing selects the typed answer, picking an option keeps the text, and the check beside the text puts it back. Focus alone never moves the answer.
  • On a multiple-choice question, the text is a note beside the selections.
  • If answers arrives with both on a single-choice question, selected wins.

Statuses

status drives the badge, the controls, and the footer. pending is the only interactive state; submitting disables every control and shows a spinner on Submit; answered collapses the card to its title and result, what the agent did with the answers. Without a result, an answered card is just its header.

Disabled

Set isDisabled on a card the conversation has moved past, such as one the user answered by typing in chat instead. Every control stops responding and the card keeps its answers on screen.

Set hasFooter={false} when the chat renders the card's actions somewhere else, such as its composer. The card still shows the questions and selections, and the chat drives step, answers, and submission itself.

Surface

The card takes the opposite of the surface it sits on, so it stands out in either chat: gray on a white background, white on a gray one. It reads the surface from the nearest Box, Row, or Column with a background token. When the parent's background is something else, such as a raw color or a plain element, set surface on a wrapping Box.

Guidelines

When to use

  • In an agent chat, when the agent needs structured input mid-task, such as a scope choice, a channel list, or a constraint, before it can continue.
  • When several related questions belong together and should be answered as one step, not scattered across the conversation.
  • When the answers should stay visible in the conversation after they're given.

When not to use

  • For sign-off on an action the agent proposes, use an approval card. This card collects answers, not approval.
  • For standalone form input in a page, use form field with regular inputs; the stepped flow only reads correctly inline in a conversation.
  • For more than a handful of questions, or ones that need validation, use a form in a drawer. A card is a quick exchange, not a wizard.

Content

  • Write each question title as one short, direct question. A long title wraps onto more lines and pushes the options down.
  • Put context the user needs to answer in the question's description, not in the title.

Accessibility

  • The card is a group named by its title, and each question's options are a group named by the question.
  • Arrow keys move through a single-choice question's options. Keyboard selection never auto-advances, since selection follows focus in a radio group.
  • After a step, focus moves to the new question's checked or first option, or to its text area when the typed answer is the chosen one. After Submit or Skip all resolves the card, focus moves to the card if it was still in the card.
  • The step counter and the result are announced as they change.
  • On a single-choice question, the text area is described as the selected answer while the typed answer is the chosen one.
  • An option is named by its label and described by its description and its image's alt. A detail input is named after its option, and typing in it never changes the option's name.
  • The image preview repeats the option's label and description, so it opens on hover only. Keyboard and screen reader users get the image's meaning from its alt.

Props

Inherits margin props.

AskUserCard

The step pair is both-or-neither. To wrap the card, type the wrapper's props as ComponentProps<typeof AskUserCard> so it keeps that rule.

NameDefaultDescription
result

—

ReactNodeWhat the agent did once the card is resolved. Omit it and the card collapses to its header, since the badge already reports the outcome.
isDisabledfalsebooleanDisables every control, for a card the conversation has moved past.
onDismiss

—

() => voidCallback for the header's dismiss button. The button renders only when provided.
title"A few questions"stringThe card's accessible name, and its visible title when no question is active. Keep it short: a long title wraps onto more lines.
status"pending""pending" | "submitting" | "answered"Where the flow stands. pending is interactive; submitting disables every control and shows a spinner on Submit; answered renders the outcome.
questions

—

AskUserQuestion[]Questions to step through, one at a time.
answers

—

Record<string, AskUserAnswer>Answers keyed by question id.
onAnswersChange

—

(answers: AskUserAnswers) => voidCallback for when any answer changes.
step

—

numberCurrent question index. Pass it with onStepChange to control the step, or pass neither to let the card own it.
onStepChange

—

(step: number) => voidCallback for when the current question index changes. Pass it with step.
onSubmit

—

(answers: AskUserAnswers) => voidCallback for when the last question is submitted, with every answer keyed by question id.
onSkipAll

—

() => voidCallback for the Skip all action. The button renders only when provided.
hasFootertruebooleanRenders the footer actions. Pass false when the surrounding chat renders them elsewhere, e.g. in its composer.
skipAllLabel"Skip all"stringLabel of the Skip all action.
nextLabel"Next"stringLabel of the advance action between questions.
submitLabel"Submit"stringLabel of the submit action on the last question.

AskUserQuestion

NameDefaultDescription
id

—

stringKey of the question's answer in answers.
title

—

stringQuestion text, shown as the card's title while the question is active. Keep it to one short, direct question: a long title wraps onto more lines.
description

—

ReactNodeSupporting copy above the options.
options

—

AskUserOption[]Choices to offer. Omit for a freeform question answered with text only.
isMultiplefalsebooleanAllow selecting several options.
isRequiredtruebooleanRequire an answer before Next or Submit enables. Pass false to let the user move on without one.
autoAdvancetruebooleanAdvance to the next question shortly after a single-choice pointer selection. Keyboard selection never auto-advances: selection follows focus in a radio group, so advancing on it would interrupt browsing.
allowCustomfalsebooleanShow a text area under the options for an answer of the user's own.
customPlaceholder"Type something else…", or "Type your answer…" on a freeform questionstringPlaceholder of the custom answer text area.

AskUserOption

NameDefaultDescription
value

—

stringValue stored in the answer when the option is selected.
label

—

stringVisible option label.
description

—

stringSupporting copy below the label.
image

—

{ | src: string; | alt?: string; | hasPreview?: boolean; | previewPlacement?: MediaPreviewProps["placement"]; | previewWidth?: MediaPreviewProps["width"]; | previewSrc?: string; | }Thumbnail beside the label, such as the mockup the option refers to.
allowDetailfalsebooleanShow a text input under the option while it is selected, for detail the label alone can't carry. The text lands in the answer's details.
detailPlaceholder"Add detail…"stringPlaceholder of the detail input.
isDisabledfalsebooleanToggles the disabled state of this option.

AskUserAnswer

NameDefaultDescription
selected

—

string[]Selected option values.
custom

—

stringTyped answer.
details

—

Record<string, string>Detail typed under selected options, keyed by option value.