Files
2026-09-11 14:59:33 -05:00

8.5 KiB

title, description, component, links
title description component links
Questionnaire A multi-step questionnaire with single-choice, multiple-choice, freeform, and skippable questions. true
source
https://github.com/huntabyte/shadcn-svelte/tree/next/sites/docs/src/lib/registry/ui/questionnaire
<script> import ComponentPreview from "$lib/components/component-preview.svelte"; import ComponentSource from "$lib/components/component-source.svelte"; import PMAddComp from "$lib/components/pm-add-comp.svelte"; import PMInstall from "$lib/components/pm-install.svelte"; import Steps from "$lib/components/steps.svelte"; import InstallTabs from "$lib/components/install-tabs.svelte"; let { viewerData } = $props(); import Step from "$lib/components/step.svelte"; </script>

Installation

{#snippet cli()} {/snippet} {#snippet manual()}

Install @shadcn-svelte/primitives:

Copy and paste the following code into your project.

{#if viewerData} {/if} {/snippet}

Usage

<script lang="ts">
  import * as Questionnaire from "$lib/components/ui/questionnaire/index.js";

  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;

  function handleSubmit(event: SubmitEvent) {
    event.preventDefault();
    const answers = new FormData(event.currentTarget as HTMLFormElement);
    // answers.get("direction"), answers.getAll(...) for multiple items.
  }
</script>

Define the collection once: pass it to Questionnaire.Root for progress, actions, and shortcuts, then map it into the parts.

<Questionnaire.Root {items} onSubmit={handleSubmit}>
  <Questionnaire.Progress />
  {#each items as question (question.name)}
    <Questionnaire.Item name={question.name} required={question.required}>
      <Questionnaire.Title>{question.prompt}</Questionnaire.Title>
      <Questionnaire.Description>{question.description}</Questionnaire.Description>
      <Questionnaire.Choices>
        {#each question.choices as choice (choice.value)}
          <Questionnaire.Choice value={choice.value}>
            <span class="font-medium">{choice.label}</span>
            {#if "description" in choice}
              <span class="text-muted-foreground">{choice.description}</span>
            {/if}
          </Questionnaire.Choice>
        {/each}
        {#if "input" in question}
          <Questionnaire.Input
            aria-label={question.input.label}
            placeholder={question.input.placeholder}
          />
        {/if}
      </Questionnaire.Choices>
      <Questionnaire.Error />
    </Questionnaire.Item>
  {/each}
  <Questionnaire.Actions>
    <Questionnaire.Previous />
    <Questionnaire.Skip />
    <Questionnaire.Next />
    <Questionnaire.Submit />
  </Questionnaire.Actions>
</Questionnaire.Root>

Composition

Questionnaire.Root
├── Questionnaire.Progress
├── Questionnaire.Item
│   ├── Questionnaire.Title
│   ├── Questionnaire.Description
│   ├── Questionnaire.Choices
│   │   ├── Questionnaire.Choice
│   │   └── Questionnaire.Input
│   └── Questionnaire.Error
└── Questionnaire.Actions
    ├── Questionnaire.Previous
    ├── Questionnaire.Skip
    ├── Questionnaire.Next
    └── Questionnaire.Submit

Questionnaire 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. See @shadcn-svelte/primitives/questionnaire for the complete behavior.

Multiple Selection

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

Freeform Answer

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

Explicit Skip

Add Questionnaire.Skip when an optional item may be intentionally left unanswered.

Shortcuts

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

Custom Validation

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

Controlled

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

Resume

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

Conditional Items

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

Navigation State

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

Custom Progress

Use the Progress child snippet to build a custom progress indicator.

Animated Items

Animate the active item while keeping progress and navigation stationary.

Card

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

Dialog

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

Accessibility

Questionnaire.Item renders a fieldset, and Questionnaire.Title 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 Questionnaire.Input an accessible name with a visible label, aria-label, or aria-labelledby. A placeholder is not a label.

Unstyled

The behavior in Questionnaire comes from the @shadcn-svelte/primitives package. Import the headless parts from @shadcn-svelte/primitives/questionnaire when you want your own markup and styles.

API Reference

The props, data attributes, and render states for every part live on the unstyled Questionnaire primitives in @shadcn-svelte/primitives/questionnaire. The styled components inherit the corresponding unstyled props. Navigation components also accept Button size and variant props, and Questionnaire.Actions is a styled-only layout helper.