Questionnaire
A motion-enhanced multi-step questionnaire with animated checkpoints, direction-aware blur transitions, validation, and keyboard shortcuts.
Installation
Pro components require registry authentication. Add your Unlumen UI Pro key as UNLUMEN_LICENSE_KEY in your .env.local file and follow the setup guide.
File Structure
Usage
import {
Questionnaire,
QuestionnaireActions,
QuestionnaireCheckpoints,
QuestionnaireChoice,
QuestionnaireChoices,
QuestionnaireDescription,
QuestionnaireError,
QuestionnaireInput,
QuestionnaireItem,
QuestionnaireNext,
QuestionnairePrevious,
QuestionnaireSkip,
QuestionnaireSubmit,
QuestionnaireTitle,
QuestionnaireViewport,
} from "@/components/unlumen-ui/questionnaire";
const items = [
{
name: "direction",
required: true,
choices: [
{ value: "delegation" },
{ value: "questions" },
{ value: "both" },
],
},
{
name: "detail",
required: false,
choices: [{ value: "focused" }, { value: "complete" }],
},
] as const;
export default function Example() {
return (
<Questionnaire
items={items}
shortcuts="letters"
onSubmit={(event) => {
event.preventDefault();
const answers = new FormData(event.currentTarget);
console.log(answers.get("direction"));
}}
>
<QuestionnaireCheckpoints />
<QuestionnaireViewport>
<QuestionnaireItem name="direction" required>
<QuestionnaireTitle>
What should we prototype next?
</QuestionnaireTitle>
<QuestionnaireDescription>
Choose a direction or write your own.
</QuestionnaireDescription>
<QuestionnaireChoices>
<QuestionnaireChoice value="delegation">
Delegation
</QuestionnaireChoice>
<QuestionnaireChoice value="questions">
Question prompts
</QuestionnaireChoice>
<QuestionnaireChoice value="both">
Both together
</QuestionnaireChoice>
<QuestionnaireInput placeholder="Another answer…" />
</QuestionnaireChoices>
<QuestionnaireError />
</QuestionnaireItem>
<QuestionnaireItem name="detail">
<QuestionnaireTitle>
How much detail should it include?
</QuestionnaireTitle>
<QuestionnaireDescription>
Skip this question if you are not sure yet.
</QuestionnaireDescription>
<QuestionnaireChoices>
<QuestionnaireChoice value="focused">Focused</QuestionnaireChoice>
<QuestionnaireChoice value="complete">
Complete flow
</QuestionnaireChoice>
</QuestionnaireChoices>
<QuestionnaireError />
</QuestionnaireItem>
</QuestionnaireViewport>
<QuestionnaireActions>
<QuestionnairePrevious />
<QuestionnaireSkip />
<QuestionnaireNext />
<QuestionnaireSubmit />
</QuestionnaireActions>
</Questionnaire>
);
}Add multiple to QuestionnaireItem when more than one fixed choice may be selected. Optional items automatically expose QuestionnaireSkip.
API Reference
Questionnaire
| Prop | Type | Default | Description |
|---|---|---|---|
items? | readonly QuestionnaireItemDefinition[] | - | Ordered item definitions used for navigation, server rendering, direction detection, and shortcuts. |
item? | string | - | Controlled name of the active item. |
defaultItem? | string | - | Initially active item in uncontrolled mode. |
onItemChange? | (item: string) => void | - | Called when navigation changes the active item. |
shortcuts? | "letters" | "numbers" | - | Assigns letter or number shortcuts to the visible choices. |
contentBlur? | number | 8 | Blur in pixels used by incoming and outgoing questions. |
slideDistance? | number | 72 | Horizontal travel in pixels. Forward and backward navigation use opposite directions. |
springStiffness? | number | 420 | Spring stiffness used by the directional slide. |
springDamping? | number | 38 | Spring damping used by the directional slide. |
className? | string | - | Additional classes applied to the form element. |
QuestionnaireViewport
| Prop | Type | Default | Description |
|---|---|---|---|
className? | string | - | Additional classes applied to the height-animated item viewport. |
QuestionnaireCheckpoints
| Prop | Type | Default | Description |
|---|---|---|---|
label? | string | "Checkpoint" | Text displayed before the animated current checkpoint. |
trackClassName? | string | - | Additional classes applied to every checkpoint track. |
indicatorClassName? | string | - | Additional classes applied to every animated checkpoint indicator. |
submissionState? | "idle" | "loading" | "success" | "idle" | Controls the animated submission layer displayed over every checkpoint, including the final one. |
submissionDuration? | number | 1.6 | Total duration in seconds used to fill or clear the submission layer across all checkpoints. |
submissionIndicatorClassName? | string | - | Additional classes applied to the submission indicator layer. |
QuestionnaireItem
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | - | Unique field name. It must match the corresponding item definition. |
required? | boolean | false | Requires an answer before navigation can continue. |
multiple? | boolean | false | Allows multiple fixed choices for this item. |
invalid? | boolean | false | Marks the item invalid from controlled validation state. |
disabled? | boolean | false | Removes the item from questionnaire navigation. |
QuestionnaireChoice
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Value submitted for the choice. |
checked? | boolean | - | Controlled selected state. |
defaultChecked? | boolean | false | Initial selected state in uncontrolled mode. |
disabled? | boolean | false | Prevents interaction with this choice. |
QuestionnaireSubmit
| Prop | Type | Default | Description |
|---|---|---|---|
submissionState? | "idle" | "loading" | "success" | "idle" | Switches the submit button between its default, loading, and green success states. |
loadingLabel? | ReactNode | "Sending response…" | Content displayed beside the animated loading icon. |
successLabel? | ReactNode | "Response sent" | Content displayed beside the success icon. |
Notes
- Pass
itemswhenever possible so backward navigation can calculate the correct animation direction and shortcuts can be server-rendered. - Keep the items inside
QuestionnaireViewport; it measures the active fieldset and spring-animates every height change instead of jumping between steps. - Answers use native form fields, so
FormDatareturns one value for single-choice items andgetAll()returns every value for multiple-choice items. - Reduced-motion preferences remove both directional travel and blur while preserving navigation and validation.
QuestionnairePrevious,QuestionnaireSkip,QuestionnaireNext, andQuestionnaireSubmithide themselves when they do not apply to the active step.
Credits
Built by leo.
The composable questionnaire behavior is based on the shadcn/ui Questionnaire.
Keep in mind
Most components on this site are inspired by or recreated from existing work across the web. I'm not here to take credit; just to learn, experiment, and sometimes push things a bit further. If something looks familiar and I forgot to mention you, reach out and I'll fix that right away.