Questionnaire
A multi-step questionnaire with single-choice, multiple-choice, freeform, and skippable questions.
Example requirements
bun add --exact sonner@2.0.8Installation
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.02. 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.mdThe temporary copy lives in .cache/shadcn-preact/questionnaire. Existing components stay in place; shared source files are updated from main.
Selected source files (33)
- button.tsx
- lib/utils.ts
- primitives/LICENSE
- primitives/button/Button.tsx
- primitives/button/index.ts
- primitives/internals/ShadcnUseRender.ts
- primitives/internals/composite/root/CompositeRootContext.ts
- primitives/internals/empty.ts
- primitives/internals/getReactElementRef.ts
- primitives/internals/getStateAttributesProps.ts
- primitives/internals/mergeObjects.ts
- primitives/internals/resolveClassName.ts
- primitives/internals/resolveStyle.ts
- primitives/internals/types.ts
- primitives/internals/useButton.ts
- primitives/internals/useFocusableWhenDisabled.ts
- primitives/internals/useIsoLayoutEffect.ts
- primitives/internals/useMergedRefs.ts
- primitives/internals/useRefWithInit.ts
- primitives/internals/useRenderElement.ts
- primitives/internals/useStableCallback.ts
- primitives/merge-props/index.ts
- primitives/questionnaire/collection.ts
- primitives/questionnaire/components.tsx
- primitives/questionnaire/context.ts
- primitives/questionnaire/index.ts
- primitives/questionnaire/types.ts
- primitives/questionnaire/use-questionnaire-choice.ts
- primitives/questionnaire/use-questionnaire-input.ts
- primitives/questionnaire/use-questionnaire-item.ts
- primitives/questionnaire/use-questionnaire-root.ts
- primitives/questionnaire/utils.ts
- questionnaire.tsx
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
Example requirements
bun add --exact sonner@2.0.8Freeform Answer
Compose QuestionnaireInput with fixed choices when the user can provide another answer.
Example requirements
bun add --exact sonner@2.0.8Example requirements
bun add --exact sonner@2.0.8Example requirements
Also uses: Native Select.
bun add --exact sonner@2.0.8Custom Validation
Combine controlled navigation with an external schema such as Zod to return to an invalid item and present its error.
Example requirements
Also uses: Card.
bun add --exact sonner@2.0.8Example requirements
bun add --exact sonner@2.0.8Example requirements
Also uses: Button.
bun add --exact sonner@2.0.8Example requirements
bun add --exact sonner@2.0.8Example requirements
bun add --exact sonner@2.0.8Example requirements
bun add --exact sonner@2.0.8Card
Compose Questionnaire with Card slots while keeping the question title and description semantic.
Example requirements
Also uses: Card.
bun add --exact sonner@2.0.8Reference
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