Questionnaire
A form that asks one question at a time, with choices, text answers, progress, skipping and keyboard shortcuts.
A questionnaire walks the reader through a set of questions, one at a time: a single choice, a
multiple choice or a short text answer per step, with a progress line and buttons to go back, skip
or continue. It is one native <form>, so the answers arrive as form data when the last step is
submitted. It is built on the headless questionnaire of @shadcn/; this package gives it the
look and leaves every word to your application.
// apps/docs/examples/ui/questionnaire.tsx'use client';
import {useState} from 'react';
import { Questionnaire, QuestionnaireActions, QuestionnaireChoice, QuestionnaireChoiceDescription, QuestionnaireChoices, QuestionnaireDescription, QuestionnaireError, QuestionnaireInput, QuestionnaireItem, QuestionnaireNext, QuestionnairePrevious, QuestionnaireProgress, QuestionnaireSkip, QuestionnaireSubmit, QuestionnaireTitle,} from '@workspace/active-ui/components/primitives/questionnaire';
/** * Three questions, one at a time: a required single choice with number * shortcuts, an optional multiple choice that can be skipped, and a text * answer. Every word is the caller's: the button labels, the progress * sentence and the error. The answers are read from the form on submit; this * example shows them instead of sending them. */export default function QuestionnaireExample() { const [answers, setAnswers] = useState<string | null>(null); return ( <div className="w-full max-w-md"> <Questionnaire shortcuts="numbers" onSubmit={(event) => { event.preventDefault(); const data = new FormData(event.currentTarget); setAnswers( JSON.stringify( { frequency: data.get('frequency'), channels: data.getAll('channels'), remarks: data.get('remarks'), }, null, 1, ), ); }} > <QuestionnaireProgress label="Questionnaire progress" formatProgress={({current, total}) => `Question ${current} of ${total}` } /> <QuestionnaireItem name="frequency" required> <QuestionnaireTitle> How often do you want a statement? </QuestionnaireTitle> <QuestionnaireChoices> <QuestionnaireChoice value="monthly"> Every month </QuestionnaireChoice> <QuestionnaireChoice value="quarterly"> Every quarter </QuestionnaireChoice> <QuestionnaireChoice value="yearly"> Once a year <QuestionnaireChoiceDescription> The minimum the law requires. </QuestionnaireChoiceDescription> </QuestionnaireChoice> </QuestionnaireChoices> <QuestionnaireError>Choose how often to continue.</QuestionnaireError> </QuestionnaireItem> <QuestionnaireItem name="channels" multiple> <QuestionnaireTitle>Where should we send it?</QuestionnaireTitle> <QuestionnaireDescription> Choose any number, or skip the question. </QuestionnaireDescription> <QuestionnaireChoices> <QuestionnaireChoice value="email">Email</QuestionnaireChoice> <QuestionnaireChoice value="portal">Portal</QuestionnaireChoice> <QuestionnaireChoice value="letter">Letter</QuestionnaireChoice> </QuestionnaireChoices> <QuestionnaireError> Choose a channel or skip this question. </QuestionnaireError> </QuestionnaireItem> <QuestionnaireItem name="remarks"> <QuestionnaireTitle>Anything else we should know?</QuestionnaireTitle> <QuestionnaireInput placeholder="Optional" /> </QuestionnaireItem> <QuestionnaireActions> <QuestionnairePrevious>Previous</QuestionnairePrevious> <QuestionnaireSkip>Skip</QuestionnaireSkip> <QuestionnaireNext>Next</QuestionnaireNext> <QuestionnaireSubmit>Submit</QuestionnaireSubmit> </QuestionnaireActions> </Questionnaire> {answers ? ( <pre className="bg-muted mt-4 rounded-md p-3 text-xs" aria-live="polite" > {answers} </pre> ) : null} </div> );}Usage#
import { Questionnaire, QuestionnaireActions, QuestionnaireChoice, QuestionnaireChoices, QuestionnaireError, QuestionnaireItem, QuestionnaireNext, QuestionnaireSubmit, QuestionnaireTitle,} from '@workspace/active-ui/components/primitives/questionnaire';
<Questionnaire onSubmit={handleSubmit}> <QuestionnaireItem name="frequency" required> <QuestionnaireTitle>How often?</QuestionnaireTitle> <QuestionnaireChoices> <QuestionnaireChoice value="monthly">Every month</QuestionnaireChoice> <QuestionnaireChoice value="yearly">Once a year</QuestionnaireChoice> </QuestionnaireChoices> <QuestionnaireError>Choose an answer to continue.</QuestionnaireError> </QuestionnaireItem> <QuestionnaireActions> <QuestionnaireNext>Next</QuestionnaireNext> <QuestionnaireSubmit>Submit</QuestionnaireSubmit> </QuestionnaireActions></Questionnaire>;How it moves#
One item at a time. Only the current
Questionnaireis shown.Item Nextmoves to the next item and puts the focus on it;Previousgoes back with the answers kept.The buttons follow the position.
Previousis hidden on the first item,Nexton the last, andSubmitappears only on the last.Skipappears only on an item that is notrequired, and marks it as skipped.Validation per item.
Nexton a required item without an answer shows itsQuestionnaireand moves the focus to the first choice; the error disappears once the reader answers.Error The answers are form data. Each item's
nameis the field name: a single choice or a text answer gives one value, amultipleitem one value per checked choice (Form). HandleData. get All onas for any form.Submit Shortcuts. With
shortcuts="numbers"(or"letters") each choice shows a key, and pressing it chooses that answer; in a multiple item it toggles.itemandoncontrol which item is shown, for example to restore a position.Item Change
Anatomy#
Questionnaire: the<form>.shortcuts,defaultorItem item, and every form attribute.Questionnaire: a progressbar with your sentence.Progress Questionnaire: one question, aItem <fieldset>.name,required,multiple.Questionnaire(theTitle <legend>) andQuestionnaire.Description QuestionnairewithChoices Questionnaireper answer, andChoice Questionnairefor a second line; orChoice Description Questionnairefor a text answer.Input Questionnaire: the message of an item that is not answered.Error QuestionnairewithActions Questionnaire,Previous Questionnaire,Skip QuestionnaireandNext Questionnaire, styled as buttons (Submit variantandsizeas on Button).
What your application provides#
Every word, in your reader's language. The package has no fallback text:
the visible label of each of the four buttons, as their children (required);
label(the accessible name) andformat(the sentence, also used asProgress aria-) ofvaluetext Questionnaire, so the word order is yours;Progress the message of each
Questionnaire, worded for a required or an optional item;Error what happens on submit: storing or sending the answers.
Accessibility and keyboard#
Each item is a
<fieldset>named by its<legend>; single choices are radio buttons, multiple choices checkboxes, under the visible indicator.Tabmoves through the choices and the buttons; the arrow keys move within a single choice.The progress is a
role="progressbar"with your sentence as its value text.After
Next, the focus moves to the new item, so a screen reader announces the new question.
API#
Questionnaire
No props of its own.
Also takes the props of @shadcn/react, unchanged and documented there: default, item, items, on, shortcuts.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireChoice
No props of its own.
Also takes the props of @shadcn/react, unchanged and documented there: checked, default, disabled, on, render, value.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireChoices
No props of its own.
Also takes the props of @shadcn/react, unchanged and documented there: render.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireDescription
No props of its own.
Also takes the props of @shadcn/react, unchanged and documented there: render.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireError
Props
- Type
ReactNode
Also takes the props of @shadcn/react, unchanged and documented there: render.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireInput
No props of its own.
Also takes the props of @shadcn/react, unchanged and documented there: render, type.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireItem
No props of its own.
Also takes the props of @shadcn/react, unchanged and documented there: invalid, multiple, name, on, required.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireNext
Props
- Type
ReactNode
- Type
"default""icon""icon-lg" "icon-sm" "icon-xs" "lg""sm""xs"null- Default
'default'
The height and padding. xs, sm, default and lg for a button with text; icon-, icon-, icon and icon- for a square button with only an icon, which then needs an accessible name (aria-).
- Type
"default""destructive""ghost""link""outline""secondary"null- Default
'default'
The visual weight of the button. default for the main action of a screen or form; outline and secondary for other actions; ghost for a quiet action in a toolbar or a list; destructive for an action that deletes or cannot be undone; link for an action that reads as a link. Every variant but link sinks 1px while pressed; a link-styled action keeps its text and icon in place (button-).
Also takes the props of @shadcn/react, unchanged and documented there: render.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnairePrevious
Props
- Type
ReactNode
- Type
"default""icon""icon-lg" "icon-sm" "icon-xs" "lg""sm""xs"null- Default
'default'
The height and padding. xs, sm, default and lg for a button with text; icon-, icon-, icon and icon- for a square button with only an icon, which then needs an accessible name (aria-).
- Type
"default""destructive""ghost""link""outline""secondary"null- Default
'outline'
The visual weight of the button. default for the main action of a screen or form; outline and secondary for other actions; ghost for a quiet action in a toolbar or a list; destructive for an action that deletes or cannot be undone; link for an action that reads as a link. Every variant but link sinks 1px while pressed; a link-styled action keeps its text and icon in place (button-).
Also takes the props of @shadcn/react, unchanged and documented there: render.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireProgress
Props
- Type
(progress) => string
The progress as a sentence in your language and word order, such as "Question 2 of 5". It is shown and is the aria-.
- Type
string
The accessible name of the progressbar, such as "Questionnaire progress".
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireSkip
Props
- Type
ReactNode
- Type
"default""icon""icon-lg" "icon-sm" "icon-xs" "lg""sm""xs"null- Default
'default'
The height and padding. xs, sm, default and lg for a button with text; icon-, icon-, icon and icon- for a square button with only an icon, which then needs an accessible name (aria-).
- Type
"default""destructive""ghost""link""outline""secondary"null- Default
'outline'
The visual weight of the button. default for the main action of a screen or form; outline and secondary for other actions; ghost for a quiet action in a toolbar or a list; destructive for an action that deletes or cannot be undone; link for an action that reads as a link. Every variant but link sinks 1px while pressed; a link-styled action keeps its text and icon in place (button-).
Also takes the props of @shadcn/react, unchanged and documented there: render.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireSubmit
Props
- Type
ReactNode
- Type
"default""icon""icon-lg" "icon-sm" "icon-xs" "lg""sm""xs"null- Default
'default'
The height and padding. xs, sm, default and lg for a button with text; icon-, icon-, icon and icon- for a square button with only an icon, which then needs an accessible name (aria-).
- Type
"default""destructive""ghost""link""outline""secondary"null- Default
'default'
The visual weight of the button. default for the main action of a screen or form; outline and secondary for other actions; ghost for a quiet action in a toolbar or a list; destructive for an action that deletes or cannot be undone; link for an action that reads as a link. Every variant but link sinks 1px while pressed; a link-styled action keeps its text and icon in place (button-).
Also takes the props of @shadcn/react, unchanged and documented there: render.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
QuestionnaireTitle
No props of its own.
Also takes the props of @shadcn/react, unchanged and documented there: render.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
Parts that take only their elementโs attributes
Questionnaire: the attributes of aActions <div>, with className and ref.Questionnaire: the attributes of aChoice Description <span>, with className and ref.
This page adapts the Questionnaire documentation of shadcn/ui (MIT licence) to this package; see Third-party notices.