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

button.tsx
slot.tsx
questionnaire.tsx

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

PropTypeDefaultDescription
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?number8Blur in pixels used by incoming and outgoing questions.
slideDistance?number72Horizontal travel in pixels. Forward and backward navigation use opposite directions.
springStiffness?number420Spring stiffness used by the directional slide.
springDamping?number38Spring damping used by the directional slide.
className?string-Additional classes applied to the form element.

QuestionnaireViewport

PropTypeDefaultDescription
className?string-Additional classes applied to the height-animated item viewport.

QuestionnaireCheckpoints

PropTypeDefaultDescription
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?number1.6Total 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

PropTypeDefaultDescription
namestring-Unique field name. It must match the corresponding item definition.
required?booleanfalseRequires an answer before navigation can continue.
multiple?booleanfalseAllows multiple fixed choices for this item.
invalid?booleanfalseMarks the item invalid from controlled validation state.
disabled?booleanfalseRemoves the item from questionnaire navigation.

QuestionnaireChoice

PropTypeDefaultDescription
valuestring-Value submitted for the choice.
checked?boolean-Controlled selected state.
defaultChecked?booleanfalseInitial selected state in uncontrolled mode.
disabled?booleanfalsePrevents interaction with this choice.

QuestionnaireSubmit

PropTypeDefaultDescription
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 items whenever 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 FormData returns one value for single-choice items and getAll() returns every value for multiple-choice items.
  • Reduced-motion preferences remove both directional travel and blur while preserving navigation and validation.
  • QuestionnairePrevious, QuestionnaireSkip, QuestionnaireNext, and QuestionnaireSubmit hide 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.