Toggle Sidebar B
AppearanceLight & dark mode D

Questionnaire

A multi-step questionnaire with single-choice, multiple-choice, freeform, and skippable questions.

Default

Dark previewReplay preview
Question 1 of 3
What should the agent build next?

Choose a direction or describe another task.

Installation

bash
npx shadcn-vue@latest add questionnaire

Usage

vue
<script setup lang="ts">
import {
  Questionnaire,
  QuestionnaireActions,
  QuestionnaireChoice,
  QuestionnaireChoices,
  QuestionnaireDescription,
  QuestionnaireError,
  QuestionnaireItem,
  QuestionnaireNext,
  QuestionnairePrevious,
  QuestionnaireProgress,
  QuestionnaireSkip,
  QuestionnaireSubmit,
  QuestionnaireTitle,
} from "@/components/ui/questionnaire";

const items = [
  { name: "direction", required: true },
  { name: "timing", required: true },
];

function handleSubmit(event: Event) {
  event.preventDefault();

  const answers = new FormData(event.target as HTMLFormElement);
  console.log(Object.fromEntries(answers));
}
</script>

<template>
  <Questionnaire :items="items" @submit="handleSubmit">
    <QuestionnaireProgress />

    <QuestionnaireItem name="direction" required>
      <QuestionnaireTitle>What should the agent build next?</QuestionnaireTitle>
      <QuestionnaireDescription>Choose a direction.</QuestionnaireDescription>
      <QuestionnaireChoices>
        <QuestionnaireChoice value="tool-calls">Tool call timeline</QuestionnaireChoice>
        <QuestionnaireChoice value="approvals">Approval checkpoints</QuestionnaireChoice>
      </QuestionnaireChoices>
      <QuestionnaireError />
    </QuestionnaireItem>

    <QuestionnaireItem name="timing" required>
      <QuestionnaireTitle>When should work begin?</QuestionnaireTitle>
      <QuestionnaireChoices>
        <QuestionnaireChoice value="now">Start now</QuestionnaireChoice>
        <QuestionnaireChoice value="backlog">Add it to the backlog</QuestionnaireChoice>
      </QuestionnaireChoices>
      <QuestionnaireError />
    </QuestionnaireItem>

    <QuestionnaireActions>
      <QuestionnairePrevious />
      <QuestionnaireSkip />
      <QuestionnaireNext />
      <QuestionnaireSubmit />
    </QuestionnaireActions>
  </Questionnaire>
</template>

Composition

Use the following composition to build a questionnaire:

Questionnaire
├── QuestionnaireProgress
├── QuestionnaireItem
│   ├── QuestionnaireTitle
│   ├── QuestionnaireDescription
│   ├── QuestionnaireChoices
│   │   ├── QuestionnaireChoice
│   │   │   └── QuestionnaireChoiceDescription
│   │   └── QuestionnaireInput
│   └── QuestionnaireError
└── QuestionnaireActions
    ├── QuestionnairePrevious
    ├── QuestionnaireSkip
    ├── QuestionnaireNext
    └── QuestionnaireSubmit

Server Rendering

Pass items to server-render the active item, progress, actions, and answer shortcuts. Without it the questionnaire only learns its order once the items have mounted on the client.

Features

  • One question at a time, with progress, navigation, and validation handled for you
  • Single-choice, multiple-choice, freeform, and intentionally skipped answers
  • Keyboard shortcuts for choices, plus arrow key navigation between questions and answers
  • Declarative items for item order, conditional items, and stable shortcut assignment
  • Controlled navigation with v-model:item for custom validation flows
  • Native form reset restores the answers you marked as defaults

Multiple Selection

Use multiple for an item that accepts more than one fixed answer.

Dark previewReplay preview
What context should the agent inspect?

Select every source that may affect the implementation.

Freeform Answer

Compose QuestionnaireInput with fixed choices when the user can provide another answer.

Dark previewReplay preview
How should the agent approach this refactor?

Choose a strategy or write a more specific instruction.

Explicit Skip

Add QuestionnaireSkip when an optional item may be intentionally left unanswered.

Dark previewReplay preview
Question 1 of 3
What kind of change is this?

Choose the category that best describes the work.

Shortcuts

Assign a letter or number key to each answer with shortcuts.

Dark previewReplay preview
What should the agent do next?

Use the displayed shortcut or navigate with the keyboard.

Custom Validation

Combine controlled navigation with an external schema such as Zod to return to an invalid item and present its error.

Dark previewReplay preview
How much detail should the answer include?

Choose the response depth.

1 / 2

Controlled

Control the active item from host state, such as returning to an invalid step.

Dark previewReplay preview

Current checkpoint: Change scope

Question 1 of 3
What may the agent change?

The host stores the active checkpoint while Questionnaire navigates.

Resume

Restore a saved active item and default answers, then reset changes back to that saved state.

Dark previewReplay preview
Question 2 of 3
How should the migration be verified?

These checks were selected during the previous session.

Conditional Items

Disable items that do not apply to the user's earlier answers.

Dark previewReplay preview
Question 1 of 2
Where should the agent run?

Cloud runs add an environment question to this flow.

Custom Progress

Use the Progress slot state to build a custom progress indicator.

Dark previewReplay preview
Checkpoint 1 of 4
How large is the change?

Animated Items

Animate the active item while keeping progress and navigation stationary.

Dark previewReplay preview
Question 1 of 3
What should the agent do?

Choose the task for this run.

Card

Compose Questionnaire with Card slots while keeping the question title and description semantic.

Dark previewReplay preview

What should the agent work on?

Choose the task that should be handled next.

Question 1 of 2

Dialog

Compose Questionnaire inside a Dialog while keeping cancellation and dismissal host-owned.

Dark previewReplay preview

Anatomy

Import all parts and piece them together.

vue
<script setup>
import {
  Questionnaire,
  QuestionnaireActions,
  QuestionnaireChoice,
  QuestionnaireChoiceDescription,
  QuestionnaireChoices,
  QuestionnaireDescription,
  QuestionnaireError,
  QuestionnaireInput,
  QuestionnaireItem,
  QuestionnaireNext,
  QuestionnairePrevious,
  QuestionnaireProgress,
  QuestionnaireSkip,
  QuestionnaireSubmit,
  QuestionnaireTitle,
} from "@/components/ui/questionnaire";
</script>

<template>
  <Questionnaire>
    <QuestionnaireProgress />
    <QuestionnaireItem>
      <QuestionnaireTitle />
      <QuestionnaireDescription />
      <QuestionnaireChoices>
        <QuestionnaireChoice>
          <QuestionnaireChoiceDescription />
        </QuestionnaireChoice>
        <QuestionnaireInput />
      </QuestionnaireChoices>
      <QuestionnaireError />
    </QuestionnaireItem>
    <QuestionnaireActions>
      <QuestionnairePrevious />
      <QuestionnaireSkip />
      <QuestionnaireNext />
      <QuestionnaireSubmit />
    </QuestionnaireActions>
  </Questionnaire>
</template>

API Reference

Questionnaire

PropTypeDefaultDescription
itemsQuestionnaireItemDefinition[]Declares item order, required, disabled, and the choices used for shortcuts.
itemstringThe active item. Use with v-model:item.
defaultItemstringThe item shown first. Ignored when item is provided.
shortcuts"letters" | "numbers"Assigns a keyboard shortcut to every choice.
noValidatebooleantrueSet to false to run native constraint validation on answered items.
classHTMLAttributes["class"]Additional classes to apply to the form.
EventDescription
update:itemThe active item changed. Payload: string.
submitEvery item is valid. Payload: Event. Call event.preventDefault() to handle it yourself.
resetThe form was reset. Payload: Event. Call event.preventDefault() to keep the answers.
SlotDescription
defaultProgress, items, and actions.

QuestionnaireProgress

PropTypeDefaultDescription
asAsTag | Component"div"The element or component to render as.
asChildbooleanfalseRender the child element instead.
classHTMLAttributes["class"]Additional classes to apply to the progress bar.
SlotDescription
defaultScoped: { current, total, first, last }. Falls back to Question N of M. Renders a named progressbar either way.

QuestionnaireItem

PropTypeDefaultDescription
namestringRequired. The name the answer submits under.
requiredbooleanfalseRequires an answer before the questionnaire can continue.
multiplebooleanfalseRenders choices as checkboxes and keeps every selected answer.
disabledbooleanfalseRemoves the item from the flow without unmounting it.
invalidbooleanfalseMarks the item invalid from outside, for example after schema validation.
classHTMLAttributes["class"]Additional classes to apply to the item.
EventDescription
update:statusThe item status changed. Payload: "unanswered" | "answered" | "skipped".
SlotDescription
defaultScoped: { active, invalid, status }. Title, description, choices, error.

QuestionnaireTitle

PropTypeDefaultDescription
asAsTag | Component"legend"The element or component to render as.
asChildbooleanfalseRender the child element instead.
classHTMLAttributes["class"]Additional classes to apply to the title.
SlotDescription
defaultThe question, rendered as a legend.

QuestionnaireDescription

PropTypeDefaultDescription
idstringOverrides the generated id.
asAsTag | Component"p"The element or component to render as.
asChildbooleanfalseRender the child element instead.
classHTMLAttributes["class"]Additional classes to apply to the description.
SlotDescription
defaultHelp text associated with the item through aria-describedby.

QuestionnaireChoices

PropTypeDefaultDescription
asAsTag | Component"div"The element or component to render as.
asChildbooleanfalseRender the child element instead.
classHTMLAttributes["class"]Additional classes to apply to the list.
SlotDescription
defaultQuestionnaireChoice items and an optional input.

QuestionnaireChoice

PropTypeDefaultDescription
valuestringRequired. The submitted value.
checkedbooleanControlled checked state. Use with v-model:checked.
defaultCheckedbooleanfalseChecks the choice on mount and after a native form reset.
disabledbooleanfalseDisables the choice.
classHTMLAttributes["class"]Additional classes to apply to the choice.
EventDescription
update:checkedThe choice was checked or cleared. Payload: boolean.
changeThe native change event. Payload: Event.
SlotDescription
defaultLabel text and QuestionnaireChoiceDescription.

QuestionnaireChoiceDescription

PropTypeDefaultDescription
classHTMLAttributes["class"]Additional classes to apply to the description.
SlotDescription
defaultSecondary text inside a choice.

QuestionnaireInput

PropTypeDefaultDescription
modelValuestring | numberControlled value. Use with v-model.
defaultValuestring | numberFills the answer on mount and after a native reset.
typeQuestionnaireInputType"text"A text-entry type: text, email, number, tel, url, search, password, date, datetime-local, month, time, or week.
disabledbooleanfalseDisables the input.
classHTMLAttributes["class"]Additional classes to apply to the input.
EventDescription
update:modelValueThe value changed. Payload: string.

QuestionnaireError

PropTypeDefaultDescription
idstringOverrides the generated id.
asAsTag | Component"p"The element or component to render as.
asChildbooleanfalseRender the child element instead.
classHTMLAttributes["class"]Additional classes to apply to the error.
SlotDescription
defaultCustom message. Falls back to a built-in one when omitted.

QuestionnaireActions

PropTypeDefaultDescription
asAsTag | Component"div"The element or component to render as.
asChildbooleanfalseRender the child element instead.
classHTMLAttributes["class"]Additional classes to apply to the row.
SlotDescription
defaultThe navigation buttons.

QuestionnairePrevious, QuestionnaireSkip, QuestionnaireNext, QuestionnaireSubmit

PropTypeDefaultDescription
variantButtonVariants["variant"]"outline"The button variant. QuestionnaireSubmit defaults to `default`.
sizeButtonVariants["size"]"default"The button size.
disabledbooleanfalseDisables the button.
asAsTag | Component"button"The element or component to render as.
asChildbooleanfalseRender the child element instead.
classHTMLAttributes["class"]Additional classes to apply to the button.
EventDescription
clickThe native click event. Call event.preventDefault() to stop the navigation. QuestionnaireSubmit has no click handler of its own.
SlotDescription
defaultButton label. Defaults to "Previous", "Skip", "Next", and "Submit".

Keyboard navigation

Questionnaire builds on native radio, checkbox, input, and button behavior. Shortcuts and arrow navigation pause while you type in a text field.

Shortcut Description
TabMoves focus between answer controls and visible actions.
Shift + TabMoves focus to the previous control or action.
Moves to the next answer. Single-choice items hand this to the browser, so a radio group keeps its native roving focus.
Moves to the previous answer. Same handover as ArrowDown: the component only steps in for checkboxes and for a text input mixed with choices.
Moves to the next question once the current one is answered or skipped, and focus is outside a radio or text entry control.
Moves to the previous question, when focus is outside a radio or text entry control.
SpaceSelects a radio, toggles a checkbox, or activates an action.
EnterContinues from a selected choice or a filled input, or activates a focused action.
Ctrl + EnterValidates and continues from anywhere inside the questionnaire, or submits on the final item. Cmd works too.
AWith shortcuts="letters", selects the matching choice. Use 1 to 9 with shortcuts="numbers".

Accessibility

ARIA behavior.

  • QuestionnaireItem renders a fieldset with a legend, so every question is announced with its answers. Descriptions and errors are associated with the item through aria-describedby, and an invalid item exposes aria-invalid.
  • QuestionnaireProgress renders a named progressbar that announces the current question. Inactive items are hidden and inert, so they stay out of the tab order and the accessibility tree.
  • Navigation actions are real buttons. QuestionnaireSubmit submits the form, so a questionnaire keeps working with browser autofill and native form submission.
  • Assigned shortcut keys and available navigation are exposed through aria-keyshortcuts.
  • Always give QuestionnaireInput an accessible name with a visible label, aria-label, or aria-labelledby. A placeholder is not a label.