View fulldotdev/ui on GitHub

Questionnaire

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

Installation

npx shadcn@latest add @fulldev/questionnaire

Usage

import {
  Questionnaire,
  QuestionnaireActions,
  QuestionnaireChoice,
  QuestionnaireChoices,
  QuestionnaireDescription,
  QuestionnaireError,
  QuestionnaireInput,
  QuestionnaireItem,
  QuestionnaireNext,
  QuestionnairePrevious,
  QuestionnaireProgress,
  QuestionnaireSkip,
  QuestionnaireSubmit,
  QuestionnaireTitle,
} from "@/components/ui/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 const
<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

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

Server Rendering

Pass items to server-render the active item, progress, actions, and answer shortcuts.

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.

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.

Read item status to opt into disabled navigation and custom action styling.

Custom Progress

Use the Progress render 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.

Accessibility

QuestionnaireItem renders a fieldset, and QuestionnaireTitle renders its legend.

API Reference

Source: react/src/components/ui/questionnaire.tsx

The props, data attributes, and render states for every part are documented on the @shadcn/react Questionnaire page.