Yugen UI

Questionnaire

Questionnaire builds surveys or multi-step forms from a config array, one question per screen or everything on one page.

Supports single, multiple, rating (uses Rating), scale (NPS 0–10), text, longText and yesNo questions, with required and custom validate. It has a progress bar, Back / Next, Enter to continue, number keys (0–9) to choose, a slide transition (off with reduced motion), a review step and a completion screen. Controlled value / onChange is supported.
Yugen Labs · experimentalThis component is outside SemVer: its API can change in a minor release, and What's new says so. Versioning policy

Quick survey

6 questions · about 1 minute

Question 1 of 617%

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.

TSX
// 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

Question 1 of 250%

Controlled & completion screen

value / onChange keep it in sync with outside state; completion is a render prop that receives answers and restart.

Usage

Do
  • 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
  • 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 · TypeDefaultDescription
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 / defaultValueQuestionnaireAnswersDefault: —Record<id, string | string[] | number | boolean>.
onChange(answers, questionId) => voidDefault: —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.
reviewbooleanDefault: trueAdds a review step before submitting.
completionReactNode | (answers, restart) => ReactNodeDefault: —Replaces the default completion screen.
title / description / submitLabelReactNode / ReactNode / stringDefault: — / — / "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 · TypeDefault (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."
requiredScorestringDefault (English)"Select a rating."messagesVi"Vui lòng chọn một mức điểm."
requiredChoicestringDefault (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 multiple.

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) => stringDefault (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 multiple có max.

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) => stringDefault (English)(value, max) => `${value}/${max} stars`messagesVi(value, max) => `${value}/${max} sao`
yesstring

Nhãn mặc định của câu yesNo.

Default (English)"Yes"messagesVi"Có"
nostringDefault (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`
keyHintYesNostringDefault (English)"Press Y or N to select"messagesVi"Nhấn Y hoặc N để chọn"
keyHintRating(max: number) => stringDefault (English)(max) => `Press 1–${max} to rate`messagesVi(max) => `Nhấn 1–${max} để chấm điểm`
keyHintScalestringDefault (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); current đếm từ 1.

Default (English)(current, total) => `Question ${current} of ${total}`messagesVi(current, total) => `Câu ${current}/${total}`
progressLabelstringDefault (English)"Survey progress"messagesVi"Tiến độ khảo sát"
progressValue(current: number, total: number) => stringDefault (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"
reviewDescriptionstringDefault (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; index đếm từ 1.

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!"
doneDescriptionstringDefault (English)"Your answers have been recorded."messagesVi"Câu trả lời của bạn đã được ghi nhận."
restartstringDefault (English)"Start over"messagesVi"Làm lại"
submitstring

Nút và trạng thái.

Default (English)"Submit"messagesVi"Gửi"
submittingstringDefault (English)"Submitting…"messagesVi"Đang gửi…"
submitErrorstringDefault (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."
reviewstringDefault (English)"Review"messagesVi"Xem lại"
backstringDefault (English)"Back"messagesVi"Quay lại"
backToEditstringDefault (English)"Back to editing"messagesVi"Quay lại chỉnh sửa"
nextstringDefault (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"