Questionnaire
Questionnaire builds surveys or multi-step forms from a config array, one question per screen or everything on one page.
Quick survey
6 questions · about 1 minute
What do you use Yugen UI for? (required)
Press 1–3 to select
Import
Install from npm, copy the source into your project with @yugen-ui/cli or the shadcn CLI, or import directly when working in this repo.
// npm install yugen-uiimport { Questionnaire, type Question } from "yugen-ui";Variants
Each tile is a standalone example: hover to copy, or press Code to see the source.
List mode + validate
mode="list": all questions on one page, each wrapped in a Field; the email is checked with validate.
Answered 0 of 2
How was your day? (required)
Press 1–5 to rate
Controlled & completion screen
value / onChange keep it in sync with outside state; completion is a render prop that receives answers and restart.
Usage
- Keep one idea per question; add a short description only when something needs explaining.
- Mark a question required only when it really is. Optional questions let people move faster.
- Turn on the review step for long surveys or when answers are hard to change after sending.
- Don't use the stepper for a simple form of 2–3 fields. Use mode="list" or plain Fields.
- Don't put more than 9 options in one question (number keys only go up to 9, and long lists are hard to read).
- Don't use validate to block an empty optional question. validate only runs once there is an answer.
Props
| Prop · Type | Default | Description |
|---|---|---|
| questionsQuestion[] | Default: — | Question config: { id, type, title, description?, required?, validate?, …per type }. |
| mode"stepper" | "list" | Default: "stepper" | One question per screen, or all on one page. |
| value / defaultValueQuestionnaireAnswers | Default: — | Record<id, string | string[] | number | boolean>. |
| onChange(answers, questionId) => void | Default: — | Called whenever an answer changes. |
| onSubmit(answers) => void | Promise<void> | Default: — | Called when every question is valid; with a Promise the Submit button shows “Submitting…”, and an error shows a retry message. |
| reviewboolean | Default: true | Adds a review step before submitting. |
| completionReactNode | (answers, restart) => ReactNode | Default: — | Replaces the default completion screen. |
| title / description / submitLabelReactNode / ReactNode / string | Default: — / — / "Submit" | Header and submit button label. |
| appearance"outline" | "subtle" | Default: "outline" | With a frame (Card) or without. |
| SingleQuestion / MultipleQuestion{ options, min?, max? } | Default: — | Option cards; multiple is limited by min / max. |
| RatingQuestion / ScaleQuestion{ max? } / { min?, max?, minLabel?, maxLabel? } | Default: — | Stars (Rating) or a segmented scale (0–10 by default). |
| TextQuestion / LongTextQuestion{ placeholder?, inputType?, maxLength?, rows? } | Default: — | Single-line Input / Textarea with a counter (when maxLength is set). |
| YesNoQuestion{ yesLabel?, noLabel? } | Default: — | Yes / No, labeled “Yes” / “No” by default; keys Y, N (or C, K). |
Messages
Text built into the component: labels, aria-labels, screen reader announcements.
English by default. Change it with the messages prop of YugenProvider, by the namespace below; messagesVi has the Vietnamese set. See Interface language.
messages.questionnaire
| Key · Type | Default (English) | messagesVi |
|---|---|---|
| requiredTextstring Lỗi khi câu bắt buộc chưa trả lời (text / longText · rating / scale · còn lại). | Default (English)"Enter an answer." | messagesVi"Vui lòng nhập câu trả lời." |
| requiredScorestring | Default (English)"Select a rating." | messagesVi"Vui lòng chọn một mức điểm." |
| requiredChoicestring | Default (English)"Select an answer." | messagesVi"Vui lòng chọn một câu trả lời." |
| minSelected(min: number) => string Lỗi số mục đã chọn của câu | Default (English)(min) => (min === 1 ? "Select at least 1 item." : `Select at least ${min} items.`) | messagesVi(min) => `Chọn ít nhất ${min} mục.` |
| maxSelected(max: number) => string | Default (English)(max) => (max === 1 ? "Select up to 1 item." : `Select up to ${max} items.`) | messagesVi(max) => `Chọn tối đa ${max} mục.` |
| selectedCount(count: number, max: number) => string Đếm số mục đã chọn của câu | Default (English)(count, max) => `${count}/${max} selected` | messagesVi(count, max) => `Đã chọn ${count}/${max}` |
| unansweredstring Trang xem lại: câu chưa trả lời, điểm rating. | Default (English)"Not answered" | messagesVi"Chưa trả lời" |
| ratingAnswer(value: number, max: number) => string | Default (English)(value, max) => `${value}/${max} stars` | messagesVi(value, max) => `${value}/${max} sao` |
| yesstring Nhãn mặc định của câu | Default (English)"Yes" | messagesVi"Có" |
| nostring | Default (English)"No" | messagesVi"Không" |
| requiredstring Chữ ẩn (sr-only) sau tiêu đề câu bắt buộc. | Default (English)"(required)" | messagesVi"(bắt buộc)" |
| keyHintChoice(count: number) => string Gợi ý phím tắt (chế độ stepper). | Default (English)(count) => `Press 1–${count} to select` | messagesVi(count) => `Nhấn 1–${count} để chọn` |
| keyHintYesNostring | Default (English)"Press Y or N to select" | messagesVi"Nhấn Y hoặc N để chọn" |
| keyHintRating(max: number) => string | Default (English)(max) => `Press 1–${max} to rate` | messagesVi(max) => `Nhấn 1–${max} để chấm điểm` |
| keyHintScalestring | Default (English)"Press a number key to select" | messagesVi"Nhấn phím số để chọn" |
| questionOf(current: number, total: number) => string Dòng tiến độ và thanh tiến độ (chế độ stepper); | Default (English)(current, total) => `Question ${current} of ${total}` | messagesVi(current, total) => `Câu ${current}/${total}` |
| progressLabelstring | Default (English)"Survey progress" | messagesVi"Tiến độ khảo sát" |
| progressValue(current: number, total: number) => string | Default (English)(current, total) => `Question ${current} of ${total}` | messagesVi(current, total) => `Câu ${current} trên ${total}` |
| reviewTitlestring Trang xem lại. | Default (English)"Review your answers" | messagesVi"Xem lại câu trả lời" |
| reviewDescriptionstring | Default (English)"Check your answers before you submit. Select “Edit” to make changes." | messagesVi"Kiểm tra lần cuối trước khi gửi. Chọn “Sửa” để thay đổi." |
| editQuestion(index: number, title: string) => string aria-label nút sửa một câu; | Default (English)(index, title) => `Edit question ${index}: ${title}` | messagesVi(index, title) => `Sửa câu ${index}: ${title}` |
| doneTitlestring Màn hình hoàn tất mặc định. | Default (English)"Thank you!" | messagesVi"Cảm ơn bạn!" |
| doneDescriptionstring | Default (English)"Your answers have been recorded." | messagesVi"Câu trả lời của bạn đã được ghi nhận." |
| restartstring | Default (English)"Start over" | messagesVi"Làm lại" |
| submitstring Nút và trạng thái. | Default (English)"Submit" | messagesVi"Gửi" |
| submittingstring | Default (English)"Submitting…" | messagesVi"Đang gửi…" |
| submitErrorstring | Default (English)"Couldn't submit your answers. Try again." | messagesVi"Không gửi được câu trả lời. Vui lòng thử lại." |
| reviewstring | Default (English)"Review" | messagesVi"Xem lại" |
| backstring | Default (English)"Back" | messagesVi"Quay lại" |
| backToEditstring | Default (English)"Back to editing" | messagesVi"Quay lại chỉnh sửa" |
| nextstring | Default (English)"Next" | messagesVi"Tiếp" |
| orPressstring Chữ trước phím Enter cạnh nút Tiếp. | Default (English)"or press" | messagesVi"hoặc nhấn" |