Survey
ProGoogle-Forms-style question cards: an eyebrow, a title with an optional required asterisk, and an answer. Survey owns the layout and semantics; the controls are the real DS components.
Installation#
Usage#
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Anatomy#
import {
SurveyQuestion,
SurveyHeader,
SurveyEyebrow,
SurveyTitle,
SurveyDescription,
SurveyOptions,
SurveyOption,
} from "@/components/ui/survey"
<SurveyQuestion>
<SurveyHeader>
<SurveyEyebrow />
<SurveyTitle />
<SurveyDescription />
</SurveyHeader>
<SurveyOptions>
<SurveyOption value="…" />
</SurveyOptions>
{/* or one answer part: SurveyShortAnswer, SurveyParagraph,
SurveyDropdown, SurveyScale, SurveyRating */}
</SurveyQuestion>Single & multiple choice#
Without a prop, SurveyOptions is a single choice: the DS RadioGroup with its canonical dot and roving focus, reporting a string. Add multiple and each row becomes a DS Checkbox reporting a string[]. The controls aren't re-styled; they're the same radio and checkbox you'd use anywhere else.
Text, paragraph & dropdown#
A question doesn't have to be a choice. SurveyShortAnswer composes the DS Input, SurveyParagraph the auto-growing Textarea (with an optional counter), and SurveyDropdown the Select for long option lists. Each is labelled by the question title automatically.
Linear scale#
SurveyScale is a 1-to-N rating row built on RadioGroup. Set min/max and optional minLabel/maxLabel captions for the ends.
Rating#
SurveyRating composes the DS Rating for a star score, labelled by the question title. It forwards every Rating prop: max, size, icon, readOnly.
Description & disabled options#
Add a SurveyDescription under the title for context, and disable any single SurveyOption: keyboard navigation skips it and its row dims.
Density#
density="compact" tightens the card padding, gaps, and row height for embedded, information-dense forms; radius and color never change. Without the prop it follows the nearest DensityProvider, and compact when there is none.
Controlled#
Pass value and onValueChange to own the selection. For a multiple group the value is a string[].
Composing a form#
There is no Survey root or provider: every SurveyQuestion is a standalone card, so a survey is just the cards you need stacked inside your own form. Pass name to a single-choice SurveyOptions, SurveyScale, or a text part and the answer lands in FormData. A multiple group owns a string[] in React state, so serialize that one yourself.
API reference#
SurveyQuestion
One standalone question card; there is no Survey root. Forwards all native <div> props; every part accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| required | boolean | false | Adds a destructive asterisk and an sr-only "(required)" to the title. |
| density | "comfortable" | "compact" | "compact" | Card padding, gaps and row height. Falls back to the nearest DensityProvider; set explicitly, it also sizes the card's text controls (sm or md). |
| Data attribute | Description |
|---|---|
| data-control-size | "sm" or "md", set only when density is passed. |
SurveyHeader
Groups the eyebrow, title and description with a tight gap. Forwards all native <div> props.
SurveyEyebrow
The small muted label above the question ("Question 1"). Forwards all native <p> props.
SurveyTitle
The question, an <h3> whose id labels the answer part. Forwards all native <h3> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| required | boolean | - | Overrides the required state inherited from SurveyQuestion. |
SurveyDescription
Muted supporting copy under the title. Forwards all native <p> props.
SurveyOptions
The choice list: a RadioGroup for a single choice, or a <fieldset> of Checkboxes with multiple. One highlight glides to the row under the cursor. Forwards native props (id, style, aria-*, data-*, handlers) to that root.
| Prop | Type | Default | Description |
|---|---|---|---|
| multiple | boolean | false | Checkboxes reporting a string[] instead of radios reporting a string. |
| value | string | string[] | - | The selection, when controlled: string[] with multiple. |
| defaultValue | string | string[] | - | The initial selection, when uncontrolled. |
| onValueChange | (value: string | string[]) => void | - | Called with the new selection: a string, or a string[] with multiple. |
| disabled | boolean | false | Disables every option in the group. |
| name | string | - | Form field name. Single choice posts one name=value; multiple posts one name=value per checked option, read with formData.getAll(name). |
| Data attribute | Description |
|---|---|
| data-multiple | Present when the group is multiple choice. |
SurveyOption
One row: a <label> around the radio or checkbox, so the whole row is the hit target. Forwards all native <label> props except onChange.
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | string | - | The value this option contributes to the selection. |
| disabled | boolean | false | Dims the row and keeps the highlight off it. |
SurveyShortAnswer
A one-line answer, the DS Input labelled by the title. Forwards all InputField props (className goes to the field).
| Prop | Type | Default | Description |
|---|---|---|---|
| rootClassName | string | - | Classes for the bordered Input container. |
SurveyParagraph
A long answer, the DS Textarea labelled by the title; grows with its text (autoResize={false} to stop it). Forwards all TextareaField props, such as showCount and maxLength.
| Prop | Type | Default | Description |
|---|---|---|---|
| rootClassName | string | - | Classes for the bordered Textarea container. |
SurveyDropdown
A single choice as the DS Select, for long option lists. Its trigger is labelled by the title.
| Prop | Type | Default | Description |
|---|---|---|---|
| options* | { value: string; label: string }[] | - | The choices, in display order. |
| placeholder | string | "Select an answer…" | Shown before a choice is made. |
| value | string | - | The selection, when controlled. |
| defaultValue | string | - | The initial selection, when uncontrolled. |
| onValueChange | (value: string) => void | - | Called with the chosen value. |
| disabled | boolean | false | Disables the dropdown. |
| name | string | - | Form field name. |
| className | string | - | Classes for the Select trigger. |
SurveyScale
A 1-to-N linear scale on a RadioGroup, with optional end captions. Forwards native <div> props to its root.
| Prop | Type | Default | Description |
|---|---|---|---|
| min | number | 1 | First value on the scale. |
| max | number | 5 | Last value on the scale. |
| minLabel | string | - | Caption under the lowest point, e.g. "Strongly disagree". |
| maxLabel | string | - | Caption under the highest point, e.g. "Strongly agree". |
| value | string | - | The chosen point as a string, when controlled. |
| defaultValue | string | - | The initial point, when uncontrolled. |
| onValueChange | (value: string) => void | - | Called with the chosen point. |
| disabled | boolean | false | Disables every point. |
| name | string | - | Form field name. Point ids come from useId, so any number of scales, named or not, can share a page. |
SurveyRating
A star score, the DS Rating labelled by the title. Forwards every Rating prop (max, size, icon, readOnly, …).
surveyVariants
The tv recipe behind every part, for styling a question of your own to match.