Questionnaire
A multi-step questionnaire with single-choice, multiple-choice, freeform, and skippable questions.
Default
Installation
npx shadcn-vue@latest add questionnaireUsage
<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
└── QuestionnaireSubmitServer 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.
Freeform Answer
Compose QuestionnaireInput with fixed choices when the user can provide another answer.
Explicit Skip
Add QuestionnaireSkip when an optional item may be intentionally left unanswered.
Shortcuts
Assign a letter or number key to each answer with shortcuts.
Custom Validation
Combine controlled navigation with an external schema such as Zod to return to an invalid item and present its error.
Controlled
Control the active item from host state, such as returning to an invalid step.
Current checkpoint: Change scope
Resume
Restore a saved active item and default answers, then reset changes back to that saved state.
Conditional Items
Disable items that do not apply to the user's earlier answers.
Custom Progress
Use the Progress slot state to build a custom progress indicator.
Animated Items
Animate the active item while keeping progress and navigation stationary.
Card
Compose Questionnaire with Card slots while keeping the question title and description semantic.
Dialog
Compose Questionnaire inside a Dialog while keeping cancellation and dismissal host-owned.
Anatomy
Import all parts and piece them together.
<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
QuestionnaireProgress
QuestionnaireItem
QuestionnaireTitle
QuestionnaireDescription
QuestionnaireChoices
QuestionnaireChoice
QuestionnaireChoiceDescription
QuestionnaireInput
QuestionnaireError
QuestionnaireActions
QuestionnairePrevious, QuestionnaireSkip, QuestionnaireNext, QuestionnaireSubmit
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.