Components

Questionnaire

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

PreviewSource
Question 1 of 3
What should the agent build next?

Choose a direction or describe another task.

Example requirements
bun add --exact sonner@2.0.8

Installation

Complete the project setup first, including Tailwind CSS v4, theme tokens, and Preact compatibility aliases.

1. Install dependencies

bun add --exact class-variance-authority@0.7.1 cn@0.4.0 lucide-preact@1.51.0 preact@11.0.0 tw-animate-css@1.4.0

2. Copy the component with degit

Run this from your project root. It selects questionnaire.tsx, its local dependencies, and both licenses directly from the repository, then copies them into src/components/ui.

bunx degit@3.10.0 https://github.com/LiasCode/shadcn-preact#main ./.cache/shadcn-preact/questionnaire --files LICENSE.md,registry/ui/button.tsx,registry/ui/lib/utils.ts,registry/ui/primitives/LICENSE,registry/ui/primitives/button/Button.tsx,registry/ui/primitives/button/index.ts,registry/ui/primitives/internals/ShadcnUseRender.ts,registry/ui/primitives/internals/composite/root/CompositeRootContext.ts,registry/ui/primitives/internals/empty.ts,registry/ui/primitives/internals/getReactElementRef.ts,registry/ui/primitives/internals/getStateAttributesProps.ts,registry/ui/primitives/internals/mergeObjects.ts,registry/ui/primitives/internals/resolveClassName.ts,registry/ui/primitives/internals/resolveStyle.ts,registry/ui/primitives/internals/types.ts,registry/ui/primitives/internals/useButton.ts,registry/ui/primitives/internals/useFocusableWhenDisabled.ts,registry/ui/primitives/internals/useIsoLayoutEffect.ts,registry/ui/primitives/internals/useMergedRefs.ts,registry/ui/primitives/internals/useRefWithInit.ts,registry/ui/primitives/internals/useRenderElement.ts,registry/ui/primitives/internals/useStableCallback.ts,registry/ui/primitives/merge-props/index.ts,registry/ui/primitives/questionnaire/collection.ts,registry/ui/primitives/questionnaire/components.tsx,registry/ui/primitives/questionnaire/context.ts,registry/ui/primitives/questionnaire/index.ts,registry/ui/primitives/questionnaire/types.ts,registry/ui/primitives/questionnaire/use-questionnaire-choice.ts,registry/ui/primitives/questionnaire/use-questionnaire-input.ts,registry/ui/primitives/questionnaire/use-questionnaire-item.ts,registry/ui/primitives/questionnaire/use-questionnaire-root.ts,registry/ui/primitives/questionnaire/utils.ts,registry/ui/questionnaire.tsx --force
mkdir -p ./src/components/ui
cp -R ./.cache/shadcn-preact/questionnaire/registry/ui/. ./src/components/ui/
cp ./.cache/shadcn-preact/questionnaire/LICENSE.md ./src/components/ui/LICENSE.md

The temporary copy lives in .cache/shadcn-preact/questionnaire. Existing components stay in place; shared source files are updated from main.

Selected source files (33)

Usage

import * as React from "preact/compat";
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.
}

Examples

Multiple Selection

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

Multiple SelectionSource
Example requirements
bun add --exact sonner@2.0.8

Freeform Answer

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

Freeform AnswerSource
Example requirements
bun add --exact sonner@2.0.8

Explicit Skip

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

Explicit SkipSource
Example requirements
bun add --exact sonner@2.0.8

Shortcuts

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

ShortcutsSource
Example requirements

Also uses: Native Select.

bun add --exact sonner@2.0.8

Custom Validation

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

Custom ValidationSource
Example requirements

Also uses: Card.

bun add --exact sonner@2.0.8

Controlled

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

ControlledSource
Example requirements
bun add --exact sonner@2.0.8

Resume

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

ResumeSource
Example requirements

Also uses: Button.

bun add --exact sonner@2.0.8

Conditional Items

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

Conditional ItemsSource
Example requirements
bun add --exact sonner@2.0.8

Navigation State

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

Navigation StateSource
Example requirements
bun add --exact sonner@2.0.8

Custom Progress

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

Custom ProgressSource
Example requirements
bun add --exact sonner@2.0.8

Animated Items

Animate the active item while keeping progress and navigation stationary.

Animated ItemsSource
Example requirements
bun add --exact sonner@2.0.8

Card

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

Example requirements

Also uses: Card.

bun add --exact sonner@2.0.8

Dialog

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

DialogSource
Example requirements

Also uses: Button, Dialog.

bun add --exact sonner@2.0.8

Reference

See the official Questionnaire documentation for composition patterns and API details. This page uses the Preact port and examples from the pinned upstream revision.

Component source