- Accordion
- Action Bar
- Action Toast
- Alert Banner
- Alert Dialog
- Alert
- Alphabet Index
- Amount Field
- Aspect Ratio
- Attachment
- Availability List
- Avatar
- Badge
- Bank Card
- Breadcrumb
- Bubble
- Button Group
- Button
- Calendar
- Camera Frame
- Card Design Picker
- Card Details
- Card Peek
- Card Stack
- Card
- Carousel
- Chart
- Chat Thread
- Checkbox
- Chip Group
- Choice Tile
- Code Block
- Code Dots
- Collapsible
- Combobox
- Command
- Context Menu
- Copy Field
- Countdown
- Country Select
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Direction
- Document Paper
- Drawer
- Dropdown Menu
- Empty
- Entity Avatar
- Field
- File Trigger
- File Upload Item
- Hover Card
- Input Group
- Input OTP
- Input
- Item
- Kbd
- Key Value
- Keypad
- Label
- Live Region
- Marker
- Message Scroller
- Message
- Money Amount
- Native Select
- Number Field
- Pagination
- Percent Split Editor
- Phone Field
- Popover
- Progress
- QR Code
- Questionnaire
- Radio Group
- Range Histogram
- Rating
- Resizable
- Scroll Area
- Search Field
- Select
- Separator
- Share Bar
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Sortable List
- Spinner
- Status Timeline
- Step Header
- Steps List
- Swipe Row
- Switch
- Table
- Tabs
- Text Field
- Textarea
- Tier Ladder
- Toggle Group
- Toggle
- Token Search Field
- Tooltip
- Trace
- Transaction Row
- Usage Meter
"use client"
import * as React from "react"
Installation#
pnpm dlx shadcn@latest add @dots/questionnaire
Usage#
import {
Questionnaire,
QuestionnaireActions,
QuestionnaireChoice,
QuestionnaireChoiceDescription,
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 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 ? (
<QuestionnaireChoiceDescription>
{choice.description}
</QuestionnaireChoiceDescription>
) : 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
โ โ โ โโโ QuestionnaireChoiceDescription
โ โ โโโ 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.
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.
"use client"
import * as React from "react"
Freeform answer#
Compose QuestionnaireInput with fixed choices when the user can provide another answer.
"use client"
import * as React from "react"
Explicit skip#
Add QuestionnaireSkip when an optional item may be intentionally left unanswered.
"use client"
import * as React from "react"
Shortcuts#
Assign a letter or number key to each answer with shortcuts.
"use client"
import * as React from "react"
Custom validation#
Combine controlled navigation with an external schema to return to an invalid item and present its error.
"use client"
import * as React from "react"
Controlled#
Control the active item from host state, such as returning to an invalid step.
Current checkpoint: Change scope
"use client"
import * as React from "react"
Resume#
Restore a saved active item and default answers, then reset changes back to that saved state.
"use client"
import * as React from "react"
Conditional items#
Disable items that do not apply to the user's earlier answers.
"use client"
import * as React from "react"
Navigation state#
Read item status to opt into disabled navigation and custom action styling.
"use client"
import * as React from "react"
Custom progress#
Use the Progress render state to build a custom progress indicator.
"use client"
import * as React from "react"
Animated items#
Animate the active item while keeping progress and navigation stationary.
"use client"
import * as React from "react"
Card#
Compose Questionnaire with Card slots while keeping the question title and description semantic.
"use client"
import * as React from "react"
Dialog#
Compose Questionnaire inside a Dialog while keeping cancellation and dismissal host-owned.
"use client"
import * as React from "react"
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.
API Reference#
Each part wraps the matching primitive from the package installed above, adds a data-slot and its styles, and passes other props through.
Questionnaire#
Renders a form. Accepts all form props except value and defaultValue.
| Prop | Type | Default |
|---|---|---|
items | QuestionnaireItemDefinition[] | - |
item | string | - |
defaultItem | string | - |
onItemChange | (item: string) => void | - |
shortcuts | "letters" | "numbers" | - |
onSubmit | (event: FormEvent) => void | - |
QuestionnaireItem#
Renders a fieldset for one question.
| Prop | Type | Default |
|---|---|---|
name | string | - |
required | boolean | - |
multiple | boolean | - |
invalid | boolean | - |
onStatusChange | (status: "unanswered" | "answered" | "skipped") => void | - |
QuestionnaireChoice#
A radio or checkbox answer with its label. Use QuestionnaireChoiceDescription inside it for a second line.
| Prop | Type | Default |
|---|---|---|
value | string | - |
checked | boolean | - |
defaultChecked | boolean | - |
disabled | boolean | - |
onChange | ChangeEventHandler<HTMLInputElement> | - |
QuestionnaireChoiceDescription#
A span for secondary text inside QuestionnaireChoice. Accepts all span props.
QuestionnaireInput#
A text input for another answer. Accepts input props except form and name. Needs an accessible name.
QuestionnaireProgress, QuestionnaireTitle, QuestionnaireDescription, QuestionnaireChoices, QuestionnaireError#
Render a div, legend, p, div and p. Accept className and a render prop.
QuestionnaireActions#
A layout div for the navigation buttons. Accepts all div props.
QuestionnairePrevious, QuestionnaireSkip, QuestionnaireNext, QuestionnaireSubmit#
Navigation buttons. They take Button variant and size and show a default label when they have no children.
| Part | variant default | size default | Label |
|---|---|---|---|
QuestionnairePrevious | "secondary" | "default" | Previous |
QuestionnaireSkip | "ghost" | "default" | Skip |
QuestionnaireNext | "default" | "default" | Next |
QuestionnaireSubmit | "default" | "default" | Submit |
On This Page
InstallationUsageCompositionServer renderingMultiple selectionFreeform answerExplicit skipShortcutsCustom validationControlledResumeConditional itemsNavigation stateCustom progressAnimated itemsCardDialogAccessibilityAPI ReferenceQuestionnaireQuestionnaireItemQuestionnaireChoiceQuestionnaireChoiceDescriptionQuestionnaireInputQuestionnaireProgress, QuestionnaireTitle, QuestionnaireDescription, QuestionnaireChoices, QuestionnaireErrorQuestionnaireActionsQuestionnairePrevious, QuestionnaireSkip, QuestionnaireNext, QuestionnaireSubmit