Skip to content
hightouchUI

Design system

39baf49

Ask user card

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- and multiple-choice 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.

Answers

Answers are { selected: string[] } per question id, and 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.

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.

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.

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.

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. 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. The header truncates it to one line and shows the full text in a tooltip.
  • 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. 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.

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 to one short line: the header truncates it, with the full text in a tooltip.
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: the header truncates it to one line, with the full text in a tooltip.
description

—

ReactNodeSupporting copy above the options.
options

—

AskUserOption[]Choices to offer.
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.

AskUserOption

NameDefaultDescription
value

—

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

—

stringVisible option label.
description

—

stringSupporting copy below the label.
isDisabledfalsebooleanToggles the disabled state of this option.