Installation
Section titled “Installation”pnpm dlx shadcn@latest add pxl-ui/registry/questionnaire
Install the following dependencies:
pnpm add @shadcn/react/questionnaire
Copy and paste the following code into your project.
Update the import paths to match your project setup.
import { Questionnaire, QuestionnaireActions, QuestionnaireChoice, QuestionnaireChoices, QuestionnaireDescription, QuestionnaireError, QuestionnaireInput, QuestionnaireItem, QuestionnaireNext, QuestionnairePrevious, QuestionnaireProgress, QuestionnaireSkip, QuestionnaireSubmit, QuestionnaireTitle,} from "@/components/ui/pxl/questionnaire"const items = [ { name: "direction", required: true, prompt: "What should we prototype next?", description: "Choose a direction or write your own.", choices: [ { value: "delegation", label: "Delegation", description: "Show how work moves to a specialist.", }, { value: "questions", label: "Question prompts", description: "Show choices while the interface waits.", }, { value: "both", label: "Both together" }, ], input: { label: "Another answer", placeholder: "Type another answer…" }, }, { name: "detail", required: false, prompt: "How much detail should it include?", description: "Skip this if you are not sure yet.", choices: [ { value: "focused", label: "Focused" }, { value: "complete", label: "Complete flow" }, ], },] as constDefine the collection once: pass it to Questionnaire for server-rendered
progress, actions, and shortcuts, then map it into the parts.
<Questionnaire items={items} onSubmit={handleSubmit}> <QuestionnaireProgress /> {items.map((question) => ( <QuestionnaireItem key={question.name} name={question.name} required={question.required} > <QuestionnaireTitle>{question.prompt}</QuestionnaireTitle> <QuestionnaireDescription> {question.description} </QuestionnaireDescription> <QuestionnaireChoices> {question.choices.map((choice) => ( <QuestionnaireChoice key={choice.value} value={choice.value}> <span className="font-medium">{choice.label}</span> {"description" in choice ? ( <span className="text-muted-foreground"> {choice.description} </span> ) : null} </QuestionnaireChoice> ))} {"input" in question ? ( <QuestionnaireInput aria-label={question.input.label} placeholder={question.input.placeholder} /> ) : null} </QuestionnaireChoices> <QuestionnaireError /> </QuestionnaireItem> ))} <QuestionnaireActions> <QuestionnairePrevious /> <QuestionnaireSkip /> <QuestionnaireNext /> <QuestionnaireSubmit /> </QuestionnaireActions></Questionnaire>function handleSubmit(event: React.FormEvent<HTMLFormElement>) { event.preventDefault() const answers = new FormData(event.currentTarget) // answers.get("direction"), answers.getAll(...) for multiple items.}Composition
Section titled “Composition”Questionnaire├── QuestionnaireProgress├── QuestionnaireItem│ ├── QuestionnaireTitle│ ├── QuestionnaireDescription│ ├── QuestionnaireChoices│ │ ├── QuestionnaireChoice│ │ └── QuestionnaireInput│ └── QuestionnaireError└── QuestionnaireActions ├── QuestionnairePrevious ├── QuestionnaireSkip ├── QuestionnaireNext └── QuestionnaireSubmitQuestionnaire owns the ordered items, active item, answer state, validation, progress, and navigation. The containing page, card, dialog, or drawer owns close and cancellation behavior, persistence, transport, and branching.
Multiple Selection
Section titled “Multiple Selection”Use multiple for an item that accepts more than one fixed answer.
Freeform Answer
Section titled “Freeform Answer”Compose QuestionnaireInput with fixed choices when the user can provide another answer.
Explicit Skip
Section titled “Explicit Skip”Add QuestionnaireSkip when an optional item may be intentionally left unanswered.
Shortcuts
Section titled “Shortcuts”Assign a letter or number key to each answer with shortcuts.
Custom Validation
Section titled “Custom Validation”Combine controlled navigation with an external schema such as Zod to return to an invalid item and present its error.
Controlled
Section titled “Controlled”Control the active item from host state, such as returning to an invalid step.
Accessibility
Section titled “Accessibility”QuestionnaireItem renders a fieldset, and QuestionnaireTitle renders its
legend. Descriptions and active errors are associated with the current item,
and invalid items and answer controls expose aria-invalid.
Fixed choices preserve native radio and checkbox behavior. Progress is exposed as a named progressbar, navigation uses real buttons, and inactive items and actions are hidden and inert. Successful navigation focuses the newly active item; failed validation focuses an available answer control.
Always give QuestionnaireInput an accessible name with a visible label,
aria-label, or aria-labelledby. A placeholder is not a label. See the
Questionnaire accessibility guide
for labeling custom compositions and the complete keyboard behavior.
Unstyled
Section titled “Unstyled”The behavior in Questionnaire comes from the @shadcn/react package. To use
it directly with your own markup and styles, see
Questionnaire under @shadcn/react.
API Reference
Section titled “API Reference”The props, data attributes, and render states for every part are documented on
the @shadcn/react Questionnaire page.
The styled components inherit the corresponding unstyled props. Navigation
components also accept Button size and variant props, and
QuestionnaireActions is a styled-only layout helper.