Questionnaire
単一選択・複数選択・自由記述・スキップ可の質問を多段で提示する shadcn/ui Questionnaire 相当の部品です(参照軸はイシュー #2001)。fandhe-frontend-headless-ui の questionnaire mod が構造・アクセシビリティ(WAI-ARIA)・表示状態(data-*)のみを提供する unstyled 部品で、Root / Progress / Question / Prompt / Description / Options / Freeform / Actions / Back / Next / Skip の 11 anatomy パーツと、count(全質問数)・step(現在位置)から質問の表示状態を導出する状態機械 Questionnaire を持ちます。
責務境界
回答値の保持・検証(必須判定)・分岐(次にどの質問へ進むか)・送信はアプリケーション側の責務であり、本部品は持ちません。部品が担うのは「現在位置に応じて質問の表示状態を切り替え、前へ / 次へ / スキップのトリガーを出す」までです。next / skip の data-disabled は、必須判定の結果(真偽)だけをアプリから引数として受け取ります。
状態モデル
question の index(0-origin)と step の比較で 3 状態(data-state)を導出します。
| 条件 | data-state |
|---|---|
index < step | completed |
index == step | active |
index > step | upcoming |
active 以外の質問には hidden 属性が付き、タブ操作・支援技術の双方から除外されます。step == count は「全質問完了」を表し、root に data-complete が付き、next / skip は無条件で無効化されます。
Skip は状態遷移としては Next と同一(min(step + 1, count) へ進む)です。「どの質問をスキップしたか」は本部品に保持せず、QuestionProps::skipped として呼び出し側が渡します。
QuestionProps
question(index, props, attrs, children) の props(QuestionProps)は 4 つの存在属性フラグを持ち、いずれも既定 false です。
| フィールド | 出力 |
|---|---|
answered | data-answered |
skipped | data-skipped |
required | data-required |
invalid | data-invalid + aria-invalid="true"(invalid が true のときのみ) |
Options / Freeform スロット
options / freeform は純スロットです。選択肢は options へ Radio Group / Checkbox Group のパーツを、自由記述は freeform へ Field の textarea パーツを入れ子にする契約とします。両者の data-scope は questionnaire scope と独立して残ります(回答ロジック自体は入れ子にした部品・アプリ側が担います)。
Progress
progress(label, attrs, children) は role="progressbar" を持ち、aria-valuemin / aria-valuemax / aria-valuenow / aria-valuetext({percent}% complete)を出力します。label が空文字でないときのみ aria-label を付与します。
アクセシビリティ
questionはfieldset要素です。prompt(legend)を先頭子に置く契約とし、ネイティブなグループ名が id 配管なしで付きます。back/next/skipはネイティブbutton(type="button")です。境界(step == 0/step == count)または呼び出し側の判定結果でdisabled+data-disabledを出力します。- キーボード操作は Tab / Shift+Tab(フォーカス移動)・Enter / Space(ボタン押下)のみで、矢印キー等の独自ハンドリングは持ちません。options / freeform のキー操作は入れ子にした部品のものを継承します。
back / next / skip の click から dispatch("prev" / "next" / "skip")への実際の DOM 配線は fandhe-frontend-wasm-full 0.18.0 以降、Runtime::mount / Runtime::hydrate が自動配線します。クリックで DOM 上の data-step ・各 question の data-state / hidden ・progress の aria-valuenow / aria-valuetext が更新され、アプリ状態へは実際に遷移した場合のみ "questionnaire:prev" / "questionnaire:next" / "questionnaire:skip"(payload は遷移前の step)が通知されます。回答値の保持・必須判定・分岐(次にどの質問へ進むか)はこの通知を受けたアプリ側の再描画で行う契約です。back / next / skip の disabled は境界(step == 0 / step == count)が変化したときのみ wasm-full が付け外しし、非境界位置でアプリが付けた disabled(必須判定結果)は保存されます。詳細は docs/design/wasm-full-architecture.md §27 を参照してください。
参考サイトとの差分
shadcn/ui の Questionnaire は回答状態管理・バリデーション・キーボードショートカット・失敗時のフォーカス移動を内包しますが、本部品はこれらを持ちません。
QuestionnaireError/QuestionnaireSubmit: 非採用です。検証結果の表示は呼び出し側が Field のエラー表示パーツを question 内へ入れ子にし、送信ボタンはactionsスロットへ通常のbuttonを置きます。QuestionnaireChoice/QuestionnaireInput/ 英数字キーによるショートカット /required/multipleの回答ロジック: 非採用です。選択肢は Radio Group / Checkbox Group、自由記述は Field のtextareaの再利用で賄います。items配列からの一括描画・回答状態管理・バリデーション・失敗時のフォーカス移動: 非採用です(アプリケーションロジック・クライアント DOM 操作の関心)。
スタイル済みの表示例(recipe・golden テスト)は Questionnaire を参照してください。
関連 API: fandhe-frontend-headless-ui API
自前 CSS の最小例
[data-scope="questionnaire"][data-part="question"] {
display: block;
}
[data-scope="questionnaire"][data-part="question"][hidden] {
display: none;
}
[data-scope="questionnaire"][data-part="question"][data-invalid] {
border-left: 2px solid #b91c1c;
padding-left: 0.75rem;
}
[data-scope="questionnaire"][data-part="next"][data-disabled],
[data-scope="questionnaire"][data-part="skip"][data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
[data-scope="questionnaire"][data-part="progress"] {
height: 0.25rem;
background: #e5e7eb;
}
[data-scope="questionnaire"][data-part="progress"][data-complete] {
background: #16a34a;
}Demo
枠線・余白は docs サイト側のデモ枠であり、fandhe-frontend-headless-ui 自体はスタイルを持ちません。
Features
- Root / Progress / Question / Prompt / Description / Options / Freeform / Actions / Back / Next / Skip の 11 anatomy パーツと、
count/stepから質問の表示状態を導出する状態機械Questionnaireを提供する(crate::steps::Stepsと同型の正規化・dispatch 契約)。 - 回答値の保持・検証(必須判定)・分岐・送信はアプリケーション責務であり本クレートは持たない。
next/skipのdata-disabledは、必須判定の結果(真偽)だけを引数として受け取る(.claude/rules/coding-rust.md§UI 部品の責務境界 規則 1)。 questionのdata-state(active/completed/upcoming)はcount/step/indexから導出する。非 active な質問はhidden属性で隠す(crate::steps::Steps::contentと同型契約)。- 選択肢は
optionsスロットへradio_group/checkbox_groupのパーツを、自由記述はfreeformスロットへfield::textareaを入れ子にする契約(両者のdata-scopeは questionnaire scope と独立して残る)。 Skipは状態遷移としてはNextと同一(min(step + 1, count))であり、「どの質問をスキップしたか」はQuestionProps::skippedとして呼び出し側が保持する。
Anatomy
root
progress
question
prompt
description
options
actions
back
next
skip
freeformAPI Reference
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
new(count, step, orientation) | usize, usize, Orientation | — | count.max(1)・step.min(count) で fail-closed 正規化する。 |
progress(label) | &str | "" | 空文字でないときのみ aria-label を付与する(shadcn「named progressbar」)。 |
question(index) | usize | — | 0-origin の質問インデックス(data-index・data-state 導出に使う)。 |
question(props) | QuestionProps | QuestionProps::default() | answered/skipped/required/invalid の 4 存在属性。invalid は aria-invalid="true" も付与する。 |
back(disabled) | bool | false | step == 0 との OR で disabled+data-disabled を決める。 |
next/skip(disabled) | bool | false | step == count との OR で disabled+data-disabled を決める(責務境界規則 1: 必須判定の結果だけを渡す)。 |
Data Attributes
| Part | Attribute | Observed Values |
|---|---|---|
next | data-disabled | (値なし) |
progress | data-complete | (値なし) |
question | data-answered | (値なし) |
question | data-index | 1 |
question | data-invalid | (値なし) |
question | data-required | (値なし) |
question | data-state | active |
root | data-complete | (値なし) |
root | data-orientation | horizontal |
root | data-step | 1, 3 |
skip | data-disabled | (値なし) |
Examples
Single choice question
options スロットへ radio_group を入れ子にした単一選択の質問。
Freeform question (invalid)
freeform スロットへ field::textarea を入れ子にし、QuestionProps::invalid で data-invalid/aria-invalid="true" を露出する。
Accessibility
Keyboard Interactions
| Key | Description |
|---|---|
| Tab / Shift+Tab | back/next/skip はネイティブ button のため既定のタブ操作を継承する。非 active な質問は hidden 属性により Tab 到達不能。 |
| Enter / Space | フォーカス中の back/next/skip ボタンを押下する(ネイティブ button の既定動作)。 |
| (実 DOM 配線) | back/next/skip の click から dispatch("prev"/"next"/"skip")への実配線は fandhe-frontend-wasm-full 側の責務(本クレートのスコープ外)。0.18.0 以降 Runtime::mount/Runtime::hydrate が自動配線し、境界での disabled 付け外し・"questionnaire:*" 通知まで行う(イシュー #2118)。 |
WAI-ARIA
| Attribute | Description |
|---|---|
| role="progressbar" (progress) | aria-valuemin/aria-valuemax/aria-valuenow/aria-valuetext を併せて出力する。 |
| aria-invalid (question) | QuestionProps::invalid が true のときのみ "true" を出力する(valid のときは属性自体を省略する)。 |
| hidden (question) | 非 active な質問へ付与し、支援技術・タブ操作の双方から除外する。 |
| disabled / data-disabled (back/next/skip) | 境界(step == 0/step == count)または呼び出し側の判定結果で活性化を抑止する。 |