MatrixChoice
Matrix choice field for surveys, NPS forms, and questionnaires
Overview
Form.Field.MatrixChoice renders a "question x answer" matrix table — the standard pattern from Google Forms and SurveyMonkey. Supports three variants: radio (single choice), checkbox (multi choice), and rating (stars).
Full example
The complete sandbox example, read directly from the form-develop-app / form-develop-app-shadcn
source at build time. The shadcn side demonstrates FieldMatrixChoice inside the broader survey
demo (alongside ImageChoice and Likert), not as a standalone page.
'use client'
import { Box, Heading, Text, VStack } from '@chakra-ui/react'
import { Form } from '@letar/forms'
import { useState } from 'react'
import { DemoPageLayout } from '../_components'
export default function MatrixChoiceDemoPage() {
const [submittedData, setSubmittedData] = useState<Record<string, unknown> | null>(null)
return (
<DemoPageLayout
title="Form.Field.MatrixChoice"
description="Матричный выбор для опросников — таблица вопрос × вариант ответа"
>
<VStack gap={8} align="stretch">
{/* Пример 1: NPS-опросник (radio) */}
<Box>
<Heading size="md" mb={3}>
NPS-опросник (radio)
</Heading>
<Text fontSize="sm" color="fg.muted" mb={4}>
Один ответ на строку. Как в Google Forms.
</Text>
<Form
debug
initialValue={{ satisfaction: {} }}
onSubmit={(data) => setSubmittedData(data as Record<string, unknown>)}
>
<Form.Field.MatrixChoice
name="satisfaction"
label="Оцените наш сервис"
rows={[
{ value: 'speed', label: 'Скорость доставки' },
{ value: 'quality', label: 'Качество товара' },
{ value: 'support', label: 'Поддержка клиентов' },
{ value: 'price', label: 'Соотношение цена/качество' },
]}
columns={[
{ value: '1', label: 'Ужасно' },
{ value: '2', label: 'Плохо' },
{ value: '3', label: 'Нормально' },
{ value: '4', label: 'Хорошо' },
{ value: '5', label: 'Отлично' },
]}
variant="radio"
/>
<Form.Button.Submit>Отправить</Form.Button.Submit>
</Form>
</Box>
{/* Пример 2: Checkbox (множественный) */}
<Box>
<Heading size="md" mb={3}>
Навыки (checkbox)
</Heading>
<Text fontSize="sm" color="fg.muted" mb={4}>
Несколько ответов на строку.
</Text>
<Form
debug
initialValue={{ skills: {} }}
onSubmit={(data) => setSubmittedData(data as Record<string, unknown>)}
>
<Form.Field.MatrixChoice
name="skills"
label="Какими навыками вы владеете?"
rows={[
{ value: 'frontend', label: 'Frontend' },
{ value: 'backend', label: 'Backend' },
{ value: 'devops', label: 'DevOps' },
]}
columns={[
{ value: 'beginner', label: 'Начинающий' },
{ value: 'intermediate', label: 'Средний' },
{ value: 'advanced', label: 'Продвинутый' },
{ value: 'expert', label: 'Эксперт' },
]}
variant="checkbox"
/>
<Form.Button.Submit>Отправить</Form.Button.Submit>
</Form>
</Box>
{/* Пример 3: Rating (звёзды) */}
<Box>
<Heading size="md" mb={3}>
Рейтинг (звёзды)
</Heading>
<Form
debug
initialValue={{ rating: {} }}
onSubmit={(data) => setSubmittedData(data as Record<string, unknown>)}
>
<Form.Field.MatrixChoice
name="rating"
label="Оцените блюда"
rows={[
{ value: 'taste', label: 'Вкус' },
{ value: 'presentation', label: 'Подача' },
{ value: 'portion', label: 'Размер порции' },
]}
columns={[
{ value: '1', label: '★' },
{ value: '2', label: '★★' },
{ value: '3', label: '★★★' },
{ value: '4', label: '★★★★' },
{ value: '5', label: '★★★★★' },
]}
variant="rating"
/>
<Form.Button.Submit>Отправить</Form.Button.Submit>
</Form>
</Box>
{submittedData && (
<Box p={4} bg="bg.subtle" borderRadius="md">
<Heading size="sm" mb={2}>
Отправленные данные:
</Heading>
<pre style={{ fontSize: '12px', overflow: 'auto' }}>{JSON.stringify(submittedData, null, 2)}</pre>
</Box>
)}
</VStack>
</DemoPageLayout>
)
}'use client'
import { FieldImageChoice, FieldLikert, FieldMatrixChoice } from '@letar/forms-shadcn'
import { useState } from 'react'
import { DemoForm, DemoPageLayout, SubmittedDataPreview } from '../_components'
const imageChoiceOptions = [
{ value: 'modern', label: 'Современный', image: 'https://placehold.co/200x120?text=Modern' },
{
value: 'classic',
label: 'Классический',
image: 'https://placehold.co/200x120?text=Classic',
description: 'Строгие линии',
},
]
const matrixRows = [
{ value: 'speed', label: 'Скорость доставки' },
{ value: 'quality', label: 'Качество товара' },
]
const matrixColumns = [
{ value: '1', label: 'Плохо' },
{ value: '3', label: 'Нормально' },
{ value: '5', label: 'Отлично' },
]
interface SurveyValues {
productStyle: string
npsScore: number | undefined
satisfaction: Record<string, string | string[]>
}
const defaultValues: SurveyValues = {
productStyle: '',
npsScore: undefined,
satisfaction: {},
}
export default function SurveyDemoPage() {
const [submitted, setSubmitted] = useState<SurveyValues | null>(null)
return (
<DemoPageLayout
title="Поля опросов"
description="ImageChoice, Likert (NPS-шкала), MatrixChoice"
>
<DemoForm<SurveyValues> defaultValues={defaultValues} onSubmit={setSubmitted}>
<FieldImageChoice name="productStyle" label="Стиль товара" options={imageChoiceOptions} columns={2} />
<FieldLikert
name="npsScore"
label="Насколько вы довольны сервисом?"
anchors={['Совсем не доволен', 'Не доволен', 'Нейтрально', 'Доволен', 'Полностью доволен']}
showNumbers
/>
<FieldMatrixChoice
name="satisfaction"
label="Оцените аспекты заказа"
rows={matrixRows}
columns={matrixColumns}
/>
<button
type="submit"
className="bg-primary text-primary-foreground rounded-md px-4 py-2 text-sm font-medium"
>
Отправить
</button>
</DemoForm>
<SubmittedDataPreview data={submitted} />
</DemoPageLayout>
)
}Vue-пример для этого поля появится позже.
Angular-пример для этого поля появится позже.
Basic Usage (Radio)
<Form initialValue={{ satisfaction: {} }} onSubmit={handleSubmit}>
<Form.Field.MatrixChoice
name="satisfaction"
label="Rate our service"
rows={[
{ value: 'speed', label: 'Delivery speed' },
{ value: 'quality', label: 'Product quality' },
{ value: 'support', label: 'Customer support' },
]}
columns={[
{ value: '1', label: 'Terrible' },
{ value: '2', label: 'Bad' },
{ value: '3', label: 'OK' },
{ value: '4', label: 'Good' },
{ value: '5', label: 'Excellent' },
]}
variant="radio"
/>
<Form.Button.Submit>Submit</Form.Button.Submit>
</Form>Value: { speed: '4', quality: '5', support: '3' }
Checkbox Variant (Multi-Select)
<Form.Field.MatrixChoice
name="skills"
variant="checkbox"
rows={[
{ value: 'frontend', label: 'Frontend' },
{ value: 'backend', label: 'Backend' },
]}
columns={[
{ value: 'beginner', label: 'Beginner' },
{ value: 'intermediate', label: 'Intermediate' },
{ value: 'expert', label: 'Expert' },
]}
/>Value: { frontend: ['beginner', 'intermediate'], backend: ['expert'] }
Rating Variant
<Form.Field.MatrixChoice
name="rating"
variant="rating"
rows={[
{ value: 'taste', label: 'Taste' },
{ value: 'presentation', label: 'Presentation' },
]}
columns={[
{ value: '1', label: '1' },
{ value: '2', label: '2' },
{ value: '3', label: '3' },
{ value: '4', label: '4' },
{ value: '5', label: '5' },
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | required | Field name |
label | string | — | Label above matrix |
rows | MatrixRow[] | required | Questions (row labels) |
columns | MatrixColumn[] | required | Answer options (column headers) |
variant | 'radio' | 'checkbox' | 'rating' | 'radio' | Selection mode |
disabled | boolean | false | Disable all inputs |
readOnly | boolean | false | Read-only mode |
Keyboard Navigation
| Key | Action |
|---|---|
| Arrow Right/Left | Move between columns |
| Arrow Up/Down | Move between rows |
| Enter / Space | Select current cell |
Required Validation
When required={true}, unfilled rows are highlighted in red after form validation triggers. Use Zod schema validation to enforce all rows are filled.
Responsive Behavior
On mobile (< md breakpoint), the table automatically switches to a card-based layout where each question is a separate card with vertically stacked options.
Live Example
Try the interactive example on forms-example.letar.best.