125k

Questionnaire

単一選択、複数選択、自由入力、意図的なスキップに対応した、アクセシブルな複数ステップのアンケートを構築します。

Questionnaire は、質問を 1 つずつ表示するためのスタイルなしフォーム プリミティブです。 回答、進捗、バリデーション、ナビゲーションを管理します。

エージェントへの確認プロンプト、オンボーディング、アンケート、受付フォーム、設定などに適しています。

スタイルなしパッケージでは、マークアップとスタイルを完全に制御できます。スタイル付きバージョンとテーマ付きの例については、 Questionnaire コンポーネントをご覧ください。

インストール

pnpm add @shadcn/react

インポート

import { Questionnaire } from "@shadcn/react/questionnaire"

Questionnaire は 1 つの名前空間から各パーツをエクスポートします。各パーツは既定要素のネイティブ props を受け取ります。

構成

<Questionnaire.Root>
  <Questionnaire.Progress />
  <Questionnaire.Item name="question">
    <Questionnaire.Title />
    <Questionnaire.Description />
    <Questionnaire.Choices>
      <Questionnaire.Choice>
        <Questionnaire.ChoiceInput />
        <Questionnaire.ChoiceLabel />
        <Questionnaire.ChoiceShortcut />
      </Questionnaire.Choice>
      <Questionnaire.Input />
    </Questionnaire.Choices>
    <Questionnaire.Error />
  </Questionnaire.Item>
  <Questionnaire.Previous />
  <Questionnaire.Skip />
  <Questionnaire.Next />
  <Questionnaire.Submit />
</Questionnaire.Root>

Root はフォームを描画します。各 Item は fieldset で、Title がその legend になります。 ChoiceInput はネイティブのラジオボタンまたはチェックボックスを描画します。

スタイル付きバージョン

スタイル付きレジストリ コンポーネントでは、フラットなコンポーネント名を使用します。

Styled componentUnstyled part
QuestionnaireQuestionnaire.Root
QuestionnaireProgressQuestionnaire.Progress
QuestionnaireItemQuestionnaire.Item
QuestionnaireTitleQuestionnaire.Title
QuestionnaireDescriptionQuestionnaire.Description
QuestionnaireChoicesQuestionnaire.Choices
QuestionnaireChoiceQuestionnaire.Choice with ChoiceInput, ChoiceLabel, and ChoiceShortcut
QuestionnaireInputQuestionnaire.Input
QuestionnaireErrorQuestionnaire.Error
QuestionnaireActionsなし。レイアウト専用です。独自のコンテナを使用してください。
QuestionnairePrevious, QuestionnaireSkip, QuestionnaireNext, QuestionnaireSubmitQuestionnaire.Previous, Questionnaire.Skip, Questionnaire.Next, Questionnaire.Submit

スタイル付きの QuestionnaireChoice は、入力、ラベル、ショートカット、視覚的インジケーターをまとめて提供します。 スタイルなしパッケージでは、これらのパーツを自分で構成します。

基本的な使い方

各 Item は 1 つのステップです。name でステップを識別し、回答用フォーム フィールドの名前になります。 Choice.value が送信される回答です。

Question 1 of 3
エージェントが次に取り組むべきことは何ですか?

方向性を選択するか、別のタスクを記述してください。

const items = [
  {
    name: "prototype",
    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
"use client"
 
import * as React from "react"
import { Questionnaire } from "@shadcn/react/questionnaire"
 
export function ProjectQuestionnaire() {
  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
 
    const formData = new FormData(event.currentTarget)
 
    console.log({
      prototype: formData.get("prototype"),
      detail: formData.get("detail"),
    })
  }
 
  return (
    <Questionnaire.Root items={items} onSubmit={handleSubmit}>
      <Questionnaire.Progress />
      {items.map((question) => (
        <Questionnaire.Item
          key={question.name}
          name={question.name}
          required={question.required}
        >
          <Questionnaire.Title>{question.prompt}</Questionnaire.Title>
          <Questionnaire.Description>
            {question.description}
          </Questionnaire.Description>
          <Questionnaire.Choices>
            {question.choices.map((choice) => (
              <Questionnaire.Choice key={choice.value} value={choice.value}>
                <Questionnaire.ChoiceInput />
                <Questionnaire.ChoiceLabel>
                  <span>{choice.label}</span>
                  {"description" in choice ? (
                    <span>{choice.description}</span>
                  ) : null}
                </Questionnaire.ChoiceLabel>
                <Questionnaire.ChoiceShortcut />
              </Questionnaire.Choice>
            ))}
            {"input" in question ? (
              <Questionnaire.Input
                aria-label={question.input.label}
                placeholder={question.input.placeholder}
              />
            ) : null}
          </Questionnaire.Choices>
          <Questionnaire.Error />
        </Questionnaire.Item>
      ))}
      <Questionnaire.Previous />
      <Questionnaire.Skip />
      <Questionnaire.Next />
      <Questionnaire.Submit />
    </Questionnaire.Root>
  )
}

Item と Choice パーツとして描画するものと同じ items コレクションを Root に渡します。 これにより、項目の順序、進捗、アクションの表示、回答ショートカットをサーバーでレンダリングした HTML から利用できます。

複数選択

multiple を指定すると、項目の固定選択肢がネイティブのチェックボックスになります。回答は FormData.getAll() で読み取ります。 アプリケーション データに multiple を保持し、描画する Item に渡してください。

エージェントはどのコンテキストを確認すべきですか?

実装に影響する可能性のあるソースをすべて選択してください。

const items = [
  {
    name: "signals",
    required: true,
    multiple: true,
    prompt: "What should every update include?",
    description: "Select all that apply.",
    choices: [
      { value: "progress", label: "Progress" },
      { value: "decisions", label: "Decisions" },
      { value: "risks", label: "Risks" },
    ],
  },
] as const
items.map((question) => (
  <Questionnaire.Item
    key={question.name}
    name={question.name}
    multiple={question.multiple}
    required={question.required}
  >
    <Questionnaire.Title>{question.prompt}</Questionnaire.Title>
    <Questionnaire.Description>
      {question.description}
    </Questionnaire.Description>
    <Questionnaire.Choices>
      {question.choices.map((choice) => (
        <Questionnaire.Choice key={choice.value} value={choice.value}>
          <Questionnaire.ChoiceInput />
          <Questionnaire.ChoiceLabel>{choice.label}</Questionnaire.ChoiceLabel>
          <Questionnaire.ChoiceShortcut />
        </Questionnaire.Choice>
      ))}
    </Questionnaire.Choices>
    <Questionnaire.Error />
  </Questionnaire.Item>
))
const signals = new FormData(form).getAll("signals").map(String)

自由入力の回答

Input は自由入力の回答を追加し、ネイティブのテキスト入力を描画します。

エージェントはこのリファクタリングにどのようにアプローチすべきですか?

戦略を選択するか、より具体的な指示を記述してください。

const items = [
  {
    name: "prototype",
    required: true,
    prompt: "What should we prototype next?",
    choices: [
      { value: "delegation", label: "Delegation" },
      { value: "questions", label: "Question prompts" },
    ],
    input: {
      label: "Another prototype direction",
      placeholder: "Type another direction…",
    },
  },
] as const
items.map((question) => (
  <Questionnaire.Item
    key={question.name}
    name={question.name}
    required={question.required}
  >
    <Questionnaire.Title>{question.prompt}</Questionnaire.Title>
    <Questionnaire.Choices>
      {question.choices.map((choice) => (
        <Questionnaire.Choice key={choice.value} value={choice.value}>
          <Questionnaire.ChoiceInput />
          <Questionnaire.ChoiceLabel>{choice.label}</Questionnaire.ChoiceLabel>
          <Questionnaire.ChoiceShortcut />
        </Questionnaire.Choice>
      ))}
      <Questionnaire.Input
        aria-label={question.input.label}
        placeholder={question.input.placeholder}
      />
    </Questionnaire.Choices>
    <Questionnaire.Error />
  </Questionnaire.Item>
))

明示的なスキップ

Skip は、任意項目を意図的に未回答のままにしたことを記録します。アプリケーションでスキップされた回答と 回答がない状態を区別する必要がある場合は、onStatusChange を使います。

Question 1 of 3
どのような変更ですか?

作業内容を最もよく表すカテゴリーを選択してください。

"use client"
 
import * as React from "react"
import {
  Questionnaire,
  type QuestionnaireItemStatus,
} from "@shadcn/react/questionnaire"
 
export function PlanningQuestionnaire() {
  const [timingStatus, setTimingStatus] =
    React.useState<QuestionnaireItemStatus>("unanswered")
 
  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
 
    const formData = new FormData(event.currentTarget)
 
    console.log({
      timing:
        timingStatus === "skipped"
          ? { status: "skipped" }
          : {
              status: "answered",
              value: formData.get("timing"),
            },
    })
  }
 
  return (
    <Questionnaire.Root defaultItem="timing" onSubmit={handleSubmit}>
      <Questionnaire.Item name="timing" onStatusChange={setTimingStatus}>
        <Questionnaire.Title>
          When should this be revisited?
        </Questionnaire.Title>
        <Questionnaire.Description>
          Skip this if timing has not been decided.
        </Questionnaire.Description>
        <Questionnaire.Choices>
          <Questionnaire.Choice value="week">
            <Questionnaire.ChoiceInput />
            <Questionnaire.ChoiceLabel>This week</Questionnaire.ChoiceLabel>
            <Questionnaire.ChoiceShortcut />
          </Questionnaire.Choice>
          <Questionnaire.Choice value="cycle">
            <Questionnaire.ChoiceInput />
            <Questionnaire.ChoiceLabel>Next cycle</Questionnaire.ChoiceLabel>
            <Questionnaire.ChoiceShortcut />
          </Questionnaire.Choice>
        </Questionnaire.Choices>
      </Questionnaire.Item>
      <Questionnaire.Skip />
      <Questionnaire.Submit />
    </Questionnaire.Root>
  )
}

回答ショートカット

shortcuts="letters" または shortcuts="numbers" を使うと、有効な固定選択肢にキーを割り当てられます。 items が指定されている場合はその順序に従います。ヒントを表示したい場所に ChoiceShortcut を配置します。

エージェントは次に何をすべきですか?

表示されたショートカットを使うか、キーボードで操作してください。

<Questionnaire.Root shortcuts="letters">
  <Questionnaire.Item name="review" required>
    <Questionnaire.Title>What should the agent review?</Questionnaire.Title>
    <Questionnaire.Choices>
      <Questionnaire.Choice value="api">
        <Questionnaire.ChoiceInput />
        <Questionnaire.ChoiceLabel>Public API</Questionnaire.ChoiceLabel>
        <Questionnaire.ChoiceShortcut />
      </Questionnaire.Choice>
      <Questionnaire.Choice value="tests">
        <Questionnaire.ChoiceInput />
        <Questionnaire.ChoiceLabel>Test coverage</Questionnaire.ChoiceLabel>
        <Questionnaire.ChoiceShortcut />
      </Questionnaire.Choice>
    </Questionnaire.Choices>
  </Questionnaire.Item>
</Questionnaire.Root>

"letters" は A から Z を、"numbers" は 1 から 9 を割り当てます。 無効な選択肢はスキップされます。ショートカットで回答を選択しても、次の項目には進みません。

バリデーション

Questionnaire は先へ進む前にアクティブな項目を検証し、フォーム送信時には有効なすべての項目を検証します。

  • 必須項目は回答されると有効になります。
  • 任意項目は回答されるか、明示的にスキップされると有効になります。
  • 無効な項目と回答は無視されます。

required は「必須」という表示テキストを追加しません。必要に応じて Title または Description に記述してください。

検証に失敗すると、Questionnaire は無効な項目を表示したままにするか開き、回答へフォーカスします。メッセージを表示するには Error を追加します。

<Questionnaire.Item name="scope" required>
  <Questionnaire.Title>
    What should the project include? (Required)
  </Questionnaire.Title>
  <Questionnaire.Choices>{/* choices */}</Questionnaire.Choices>
  <Questionnaire.Error />
</Questionnaire.Item>

Error は項目が無効になるまで非表示です。既定メッセージを置き換えるには children を渡します。

<Questionnaire.Error>Please choose a project scope.</Questionnaire.Error>

Error がなくても検証は動作します。描画されると、メッセージはスクリーン リーダーに通知されます。

Zod などの外部バリデーターを使う場合は、Item.invalid を設定し、Error にメッセージを描画して、 Root.item を最初の無効な項目へ移動します。

回答にはどの程度の詳細を含めるべきですか?

回答の詳細度を選択してください。

1 / 2
<Questionnaire.Root item={item} onItemChange={setItem} onSubmit={handleSubmit}>
  <Questionnaire.Item invalid={Boolean(errors.detail)} name="detail" required>
    <Questionnaire.Title>How much detail?</Questionnaire.Title>
    <Questionnaire.Choices>{/* choices */}</Questionnaire.Choices>
    <Questionnaire.Error>{errors.detail}</Questionnaire.Error>
  </Questionnaire.Item>
</Questionnaire.Root>

ナビゲーションの制御

アクティブな項目を制御するには、item と onItemChange を渡します。

現在のチェックポイント: 変更範囲

Question 1 of 3
エージェントは何を変更してよいですか?

Questionnaireがナビゲートする間、ホストがアクティブなチェックポイントを保持します。

const [item, setItem] = React.useState("scope")
 
<Questionnaire.Root item={item} onItemChange={setItem} onSubmit={handleSubmit}>
  <Questionnaire.Item name="scope" required>
    {/* question and answers */}
  </Questionnaire.Item>
  <Questionnaire.Item name="verification" required>
    {/* question and answers */}
  </Questionnaire.Item>
  <Questionnaire.Previous />
  <Questionnaire.Next>Next</Questionnaire.Next>
  <Questionnaire.Submit>Submit</Questionnaire.Submit>
</Questionnaire.Root>

既定値から再開する

defaultItem、defaultChecked、defaultValue を使って保存済みの下書きを復元します。

Question 2 of 3
移行はどのように検証しますか?

これらのチェックは前回のセッションで選択されました。

<Questionnaire.Root defaultItem="verification">
  <Questionnaire.Item name="scope" required>
    <Questionnaire.Title>Which files are in scope?</Questionnaire.Title>
    <Questionnaire.Choices>
      <Questionnaire.Choice value="component" defaultChecked>
        <Questionnaire.ChoiceInput />
        <Questionnaire.ChoiceLabel>Component only</Questionnaire.ChoiceLabel>
      </Questionnaire.Choice>
    </Questionnaire.Choices>
  </Questionnaire.Item>
  <Questionnaire.Item name="verification" required>
    <Questionnaire.Title>Any extra instructions?</Questionnaire.Title>
    <Questionnaire.Input
      aria-label="Extra instructions"
      defaultValue="Run the package tests."
    />
  </Questionnaire.Item>
  <button type="reset">Reset draft</button>
</Questionnaire.Root>

条件付き項目

disabled を設定すると、現在のフローから項目を除外できます。無効な項目は進捗、ナビゲーション、検証、送信から除外されます。

Question 1 of 2
エージェントはどこで実行すべきですか?

クラウド実行を選択すると、このフローに環境の質問が追加されます。

const [runtime, setRuntime] = React.useState("local")
 
<Questionnaire.Root>
  <Questionnaire.Item name="runtime" required>
    <Questionnaire.Title>Where should the agent run?</Questionnaire.Title>
    <Questionnaire.Choices>
      <Questionnaire.Choice
        value="local"
        checked={runtime === "local"}
        onChange={() => setRuntime("local")}
      >
        <Questionnaire.ChoiceInput />
        <Questionnaire.ChoiceLabel>Locally</Questionnaire.ChoiceLabel>
      </Questionnaire.Choice>
      <Questionnaire.Choice
        value="remote"
        checked={runtime === "remote"}
        onChange={() => setRuntime("remote")}
      >
        <Questionnaire.ChoiceInput />
        <Questionnaire.ChoiceLabel>
          Remote environment
        </Questionnaire.ChoiceLabel>
      </Questionnaire.Choice>
    </Questionnaire.Choices>
  </Questionnaire.Item>
  <Questionnaire.Item name="region" disabled={runtime !== "remote"} required>
    {/* remote-only question */}
  </Questionnaire.Item>
</Questionnaire.Root>

ナビゲーション状態

ナビゲーション アクションは既定で有効です。そのため、次へまたは送信を実行すると検証エラーを表示できます。 アクションを自分で無効にしたい場合は、レンダー状態を使います。

Question 1 of 2
エージェントは何を変更できますか?

回答が選択されるまで、次へボタンは意図的に無効化されています。

<Questionnaire.Next
  render={(props, state) => (
    <button {...props} disabled={state.status === "unanswered"} />
  )}
>
  Next
</Questionnaire.Next>

レンダー状態には visible、disabled、shortcut、アクティブな項目の status が含まれます。 見た目だけを変更する場合は、代わりに data-status を対象にします。

<Questionnaire.Next data-navigation-action>Next</Questionnaire.Next>
[data-navigation-action][data-status="unanswered"] {
  opacity: 0.5;
}

カスタム進捗

Progress は既定で Question {current} of {total} を描画します。要素や形式を変更するには render を使います。

チェックポイント 1 / 4
変更の規模はどのくらいですか?
<Questionnaire.Progress
  aria-label="Setup progress"
  render={(props, state) => (
    <output {...props}>
      Checkpoint {state.current} of {state.total}
    </output>
  )}
/>

レンダー状態には current、total、first、last が含まれます。必要に応じてローカライズした aria-label を渡してください。

アニメーション付き項目

進捗とナビゲーションを固定したまま、アクティブな項目をアニメーションさせます。

Question 1 of 3
エージェントは何をすべきですか?

この実行のタスクを選択してください。

const itemClassName =
  "data-active:animate-in data-active:fade-in-0 data-active:slide-in-from-bottom-2 data-active:duration-300 motion-reduce:animate-none"
 
<Questionnaire.Item
  className={itemClassName}
  name="task"
  required
>
  {/* ... */}
</Questionnaire.Item>

非アクティブな項目はすぐに非表示になるため、表示時だけアニメーションさせます。

カードとの構成

カードの周囲にルートを配置し、Title と Description をカード ヘッダーに描画します。 既定の legend を置き換えるため、タイトルに id を付けて項目の aria-labelledby と関連付けます。

エージェントは何に取り組むべきですか?
次に処理すべきタスクを選択してください。
Question 1 of 2
const titleId = React.useId()
 
<Questionnaire.Root onSubmit={handleSubmit}>
  <Card>
    <Questionnaire.Item aria-labelledby={titleId} name="task" required>
      <CardHeader>
        <Questionnaire.Title id={titleId} render={<CardTitle />}>
          What should the agent work on?
        </Questionnaire.Title>
        <Questionnaire.Description render={<CardDescription />}>
          Choose the task that should be handled next.
        </Questionnaire.Description>
        <CardAction>
          <Questionnaire.Progress />
        </CardAction>
      </CardHeader>
      <CardContent>
        <Questionnaire.Choices>{/* choices */}</Questionnaire.Choices>
        <Questionnaire.Error />
      </CardContent>
    </Questionnaire.Item>
    <CardFooter>
      <Questionnaire.Next>Next</Questionnaire.Next>
      <Questionnaire.Submit>Submit</Questionnaire.Submit>
    </CardFooter>
  </Card>
</Questionnaire.Root>

ダイアログとの構成

ダイアログの終了と Skip は分けて扱います。閉じる操作はフローをキャンセルし、スキップは意図的な未回答項目として記録します。

const titleId = React.useId()
 
<Dialog>
  <DialogTrigger>Open clarification</DialogTrigger>
  <DialogContent>
    <Questionnaire.Root onSubmit={handleSubmit}>
      <Questionnaire.Item aria-labelledby={titleId} name="scope" required>
        <DialogHeader>
          <Questionnaire.Progress />
          <Questionnaire.Title id={titleId} render={<DialogTitle />}>
            Which files are in scope?
          </Questionnaire.Title>
          <Questionnaire.Description render={<DialogDescription />}>
            Choose how broadly the agent can update the workspace.
          </Questionnaire.Description>
        </DialogHeader>
        <Questionnaire.Choices>{/* choices */}</Questionnaire.Choices>
        <Questionnaire.Error />
      </Questionnaire.Item>
      <DialogFooter>
        <DialogClose>Cancel</DialogClose>
        <Questionnaire.Next>Next</Questionnaire.Next>
        <Questionnaire.Submit>Send answer</Questionnaire.Submit>
      </DialogFooter>
    </Questionnaire.Root>
  </DialogContent>
</Dialog>

カスタム描画先

render を使って、パーツの既定要素を置き換えます。要素を渡すか、状態が必要な場合は関数を使います。

<Questionnaire.Next render={<Button />}>Next</Questionnaire.Next>

Root と Item は常に form と fieldset を描画します。Title が legend を描画しなくなる場合は、 id を付け、その ID を aria-labelledby で Item に渡してください。

プリミティブは data-slot を出力しません。スロットと視覚的インジケーターはスタイル付きラッパーが管理します。

ネイティブフォーム

Root は常にネイティブ フォームを描画し、onSubmit、onReset、action、その他のフォーム props をサポートします。 Questionnaire を別のフォームの中にネストしないでください。

回答はネイティブ コントロールを通じてシリアライズされます。

  • FormData.get(itemName) は単一の回答を読み取ります。
  • FormData.getAll(itemName) は複数の回答を読み取ります。
  • スキップされた項目は FormData に含まれません。
  • スキップと未回答を区別するには Item.onStatusChange を使います。

Root は既定で noValidate を設定するため、ブラウザーの制約検証 UI ではなく Questionnaire.Error を使って検証します。

form.reset() は初期項目、既定の回答、スキップ状態、検証状態を復元します。

キーボード操作

Questionnaire はネイティブのラジオボタン、チェックボックス、入力、ボタンの動作を基盤にしています。

キー動作
Tab回答コントロールと表示中のアクション間でフォーカスを移動します。
Shift + Tab前のコントロールまたはアクションへフォーカスを移動します。
ArrowUp項目、固定回答、空のテキスト形式の自由入力から前の回答へ移動します。ネイティブ ラジオでは選択も行います。
ArrowDown項目、固定回答、空のテキスト形式の自由入力から次の回答へ移動します。ネイティブ ラジオでは選択も行います。
ArrowLeftラジオやテキスト入力の外側にフォーカスがある場合、前の項目へ移動します。
ArrowRightアクティブな項目が回答済みまたはスキップ済みで、ラジオやテキスト入力の外側にフォーカスがある場合、次の項目へ移動します。
Spaceラジオを選択、チェックボックスを切り替え、またはフォーカス中のアクションを実行します。
Enter選択済みの選択肢または入力済みの入力から続行し、フォーカス中のアクションを実行します。
Command/Ctrl + Enterアンケート内のどこからでも検証して続行するか、最後の項目を送信します。

テキスト フィールドへの入力中はショートカットと矢印ナビゲーションが一時停止します。 ルートの onKeyDown イベントを preventDefault すると、アンケートのキー処理が無効になります。

ナビゲーション アクションは既定で有効なため、操作を試みると検証フィードバックを表示できます。 未回答のアクションを無効化または再スタイルするには、ナビゲーション状態を参照してください。

アクセシビリティ

  • Item は fieldset を描画します。
  • Title は既定で fieldset の legend を描画します。カスタム描画先を使う場合は、id を aria-labelledby で項目に関連付けます。
  • Description とアクティブな Error は aria-describedby で関連付けられます。
  • 無効な項目と回答コントロールは aria-invalid を公開します。
  • Progress は現在値、最小値、最大値、テキスト値を持つ名前付き progressbar を描画します。
  • 固定選択肢にはネイティブのラジオボタンとチェックボックスを使います。
  • 割り当てられたショートカット キーと利用可能なナビゲーションは aria-keyshortcuts で公開されます。
  • 非アクティブな項目とアクションは非表示かつ inert です。
  • ナビゲーションでは、新しくアクティブになった fieldset にフォーカスします。
  • 検証では、最初に利用可能な回答コントロールにフォーカスします。
  • 無効な項目は進捗とナビゲーションから除外されます。

Input には常にアクセシブルな名前を付けます。明示的な id と表示ラベルを使います。

<Label htmlFor="other-answer">Other answer</Label>
<Questionnaire.Input
  id="other-answer"
  placeholder="Type another answer…"
/>

デザイン上表示ラベルがない場合は、aria-label または aria-labelledby を使います。

<Questionnaire.Input
  aria-label="Other answer"
  placeholder="Type another answer…"
/>

プレースホルダーはラベルではありません。

データ属性

これらの属性をスタイル設定に使います。真偽値属性は true のときに存在し、false のときは存在しません。

Root と Progress

Data attributeValue
data-currentアクティブな項目の 1 始まりの位置。
data-total有効な項目の数。
data-first最初の項目に存在します。
data-last最後の項目に存在します。
data-shortcutsRoot で有効な場合の "letters" または "numbers"。

Item

Data attributeValue
data-activeアクティブなときに存在します。
data-status"unanswered"、"answered"、"skipped" のいずれか。
data-required必須のときに存在します。
data-multiple複数選択のときに存在します。
data-disabled無効なときに存在します。
data-invalid検証に失敗した後に存在します。

Choice

Data attributeValue
data-type"radio" または "checkbox"。
data-checked選択されているときに存在します。
data-unchecked選択されていないときに存在します。
data-disabled無効なときに存在します。
data-invalid項目が無効なときに存在します。
data-shortcut割り当てられた文字または数字。

Choices

Data attributeValue
data-shortcuts有効な場合の "letters" または "numbers"。

Input

Data attributeValue
data-filled入力に空でないテキストがあるときに存在します。
data-empty入力に回答テキストがないときに存在します。
data-disabled無効なときに存在します。
data-invalid項目が無効なときに存在します。

Error

Data attributeValue
data-invalidエラーがアクティブな間に存在します。

Previous、Skip、Next、Submit

Data attributeValue
data-visibleアクションがアクティブな項目に適用されるときに存在します。
data-hiddenアクションが適用されないときに存在します。
data-disabled無効なときに存在します。
data-shortcut有効で表示中の Next または Submit における "Enter"。
data-statusアクティブな項目の "unanswered"、"answered"、または "skipped"。

Title と Description に状態属性はありません。スタイルなしパーツは data-slot を出力しません。

API リファレンス

Questionnaire.Root

フォームと調整を担うルートです。

PropTypeDefaultDescription
itemstring-アクティブな項目名を制御します。
defaultItemstringfirst enabled item初期状態でアクティブにする項目名です。
itemsreadonly QuestionnaireItemDefinition[]-サーバー レンダリングと項目順序に使う任意の項目データです。
onItemChange(item: string) => void-ナビゲーションで別の項目が要求されたときに呼び出します。
shortcuts"letters" | "numbers"-定義または描画された DOM の順序で、範囲付きの回答ショートカットを割り当てます。
noValidatebooleantrueアンケートの検証を維持しながら、ネイティブの制約検証 UI を無効にします。
onSubmitFormEventHandler<HTMLFormElement>-すべての項目の検証後に実行されるネイティブ フォーム送信ハンドラーです。
onResetFormEventHandler<HTMLFormElement>-ネイティブのリセット ハンドラーです。アンケートのリセットを止めるには preventDefault します。

Root は current、total、first、last、shortcuts をデータ属性で公開します。

任意のコレクション型は @shadcn/react/questionnaire からエクスポートされます。

type QuestionnaireChoiceDefinition = {
  value: string
  disabled?: boolean
}
 
type QuestionnaireItemDefinition = {
  name: string
  required?: boolean
  disabled?: boolean
  choices?: readonly QuestionnaireChoiceDefinition[]
}

Questionnaire.Progress

有効な項目コレクション内の位置です。

PropTypeDefaultDescription
childrenReact.ReactNodeQuestion {current} of {total}進捗コンテンツをカスタマイズします。
aria-labelstringQuestionnaire progressprogressbar のアクセシブルな名前です。
renderReactElement | (props, state) => ReactElement<div>進捗状態を持つカスタム描画先です。

レンダー状態には current、total、first、last が含まれます。

Questionnaire.Item

アンケートの 1 つのステップです。

PropTypeDefaultDescription
namestringrequired一意な項目識別子であり、ネイティブ フォーム フィールド名です。
invalidbooleanfalse外部バリデーターによって項目を無効としてマークします。
requiredbooleanfalse回答を必須にし、Skip を無効にします。
multiplebooleanfalse固定選択肢をチェックボックスとして描画します。
disabledbooleanfalse項目を進捗とナビゲーションから除外します。
onStatusChange(status: QuestionnaireItemStatus) => void-"unanswered"、"answered"、"skipped" の変化を監視します。

Item は active、disabled、invalid、multiple、required、status をデータ属性で公開します。

Item 名は Root 内で一意でなければなりません。名前は fieldset ではなく回答コントロールに属します。

Questionnaire.Title

項目のタイトルです。既定ではセマンティックな legend を描画します。

PropTypeDefaultDescription
renderReactElement | render function<legend>カスタム描画先です。その id を項目の aria-labelledby と組み合わせます。

Questionnaire.Description

aria-describedby で項目に関連付ける補足テキストです。

PropTypeDefaultDescription
renderReactElement | render function<p>カスタム描画先です。

Questionnaire.Choices

固定回答と自由入力回答のレイアウト コンテナです。

PropTypeDefaultDescription
renderReactElement | render function<div>カスタム描画先です。

レンダー状態には shortcuts が含まれます。

Questionnaire.Choice

固定回答のコンテナです。中に ChoiceInput、ChoiceLabel、必要に応じて ChoiceShortcut を 1 つずつ配置します。 既定の <label> により、行全体がネイティブ コントロールに関連付けられます。

PropTypeDefaultDescription
valuestringrequiredネイティブ回答値です。
checkedboolean-選択状態を制御します。
defaultCheckedbooleanfalse初期選択状態です。
disabledbooleanfalseネイティブ回答コントロールを無効にします。
onChangeChangeEventHandler<HTMLInputElement>-ネイティブ入力の変更ハンドラーです。
renderReactElement | (props, state) => ReactElement<label>選択状態を持つカスタム描画先です。

レンダー状態には checked、disabled、invalid、shortcut、type が含まれます。

Questionnaire.ChoiceInput

所属する Choice のネイティブ ラジオボタンまたはチェックボックスです。Questionnaire が選択、フォーム送信、検証、 キーボード操作に必要な props を提供します。className、id、ref、render、その他競合しないネイティブ入力 props も受け取ります。

ChoiceInput は Choice の内部で使う必要があります。レンダー状態は Choice と同じです。

Questionnaire.ChoiceLabel

固定選択肢の表示ラベル コンテンツです。Choice のラベル内に <span> を描画し、ネイティブ span props と render を受け取ります。

Questionnaire.ChoiceShortcut

固定選択肢の表示ショートカットです。割り当てられた文字または数字を含む <span> を描画し、選択肢にショートカットがない場合は非表示のままです。 ネイティブ span props と render を受け取り、レンダー状態には shortcut が含まれます。

Questionnaire.Input

自由入力の回答です。ネイティブの name は所属する項目が管理します。

PropTypeDefaultDescription
valuestring | number | readonly string[]-ネイティブ入力値を制御します。
defaultValuestring | number | readonly string[]-初期ネイティブ入力値です。
disabledbooleanfalse入力を無効にします。
onChangeChangeEventHandler<HTMLInputElement>-ネイティブ入力の変更ハンドラーです。
typeQuestionnaireInputType"text""email"、"number"、"date" などのテキスト入力型です。
renderReactElement | (props, state) => ReactElement<input>入力状態を持つカスタム描画先です。

レンダー状態には disabled、filled、invalid が含まれます。

Questionnaire.Error

検証メッセージです。項目の検証に失敗するまで非表示です。

PropTypeDefaultDescription
childrenReact.ReactNodecontextual messageカスタム検証メッセージです。
renderReactElement | (props, state) => ReactElement<p>無効状態を持つカスタム描画先です。

ナビゲーション アクション

Previous、Skip、Next、Submit はネイティブ ボタンを描画し、表示状態が変わってもマウントされたままです。

PartDefault typeVisible whenDisabled when
Previous"button"最初の項目ではないとき。利用側が disabled を設定します。
Skip"button"アクティブな項目が任意のとき。利用側が disabled を設定します。
Next"button"最後の項目ではないとき。利用側が disabled を設定します。
Submit"submit"最後の項目のとき。利用側が disabled を設定します。

各パーツはネイティブ ボタン props と次の props を受け取ります。

PropTypeDescription
renderReactElement | (props, state) => ReactElementナビゲーション状態を持つカスタム描画先です。

レンダー状態には visible、disabled、shortcut、アクティブな項目の status が含まれます。