Работу делают нативные поля
Каждая часть поля рендерит настоящий элемент: TextFieldInput, PasswordFieldInput, NumberFieldInput, PhoneFieldInput, DateFieldInput и TimeFieldInput — это <input>, TextArea — <textarea>, Radio — <input type="radio">, а CheckboxInput, SwitchInput, SegmentedControlItemInput и SliderThumbInput — поля внутри своих компонентов. PinField хранит код в одном визуально скрытом поле за своими ячейками.
Поэтому формы получают поведение браузера бесплатно:
name,value,required,pattern,min,max,minLengthиautoCompleteставятся на часть-поле, ровно как на нативный элементnew FormData(form)читает значения, а отправка безpreventDefault()передаёт их, как любая HTML-форма- у
Buttonнетtypeпо умолчанию, поэтому внутри формы она отправляет её, как нативная<button>; передайтеtype='button'кнопкам, которые не должны этого делать - автозаполнение браузера, менеджеры паролей и атрибут
formработают с полями как обычно
import { Button, Label, Text, TextField, TextFieldInput } from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';
export default function ProfileFormSubmit() {
const id = useId();
const [saved, setSaved] = useState('');
const onSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
setSaved(`${data.get('fullName')} <${data.get('email')}>`);
};Что отправляет каждое поле
Нативное поле отправляет текст, который показывает. Для большинства полей это и есть значение; несколько компонентов держат на корне другое значение:
| Компонент | Отправляется при name на поле | Значение на корне |
|---|---|---|
TextField, TextArea, PasswordField | введённый текст | — |
NumberField | отформатированный текст, например 2 500 | value / defaultValue числом, NaN для пустого поля |
DateField, DateRangeField, DateTimeField, TimeField | введённый текст в format, например 15.01.2026 | ISO value / defaultValue, см. Даты и календарные системы |
Checkbox, Radio, Switch, SegmentedControl | value каждого отмеченного поля | — |
Slider | значение каждого бегунка; name на корне применяется ко всем бегункам | value / defaultValue массивом |
Select | выбранное значение через SelectHiddenSelect с name | value / defaultValue |
Если сервер ждёт значение корня, а не показанный текст, повторите его в скрытом поле из onChangeValue. Так пример DateField отправляет ISO-день. У Listbox нет части для формы: рендерите по скрытому полю на каждое выбранное значение, как в Listbox в форме.
import {
Button,
Label,
Select,
SelectCombobox,
SelectHiddenSelect,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
Text,
} from '@vega-ui/react';Валидация ограничений
required, pattern, type='email', min и max на частях-полях блокируют отправку и заполняют input.validity, как у нативных полей. Браузер показывает своё всплывающее сообщение, если вы не возьмёте это на себя:
- слушайте
onInvalidна поле и вызывайтеevent.preventDefault(), чтобы скрыть подсказку браузера, а затем читайтеevent.currentTarget.validity, чтобы выбрать своё сообщение - или поставьте
noValidateна форму и проверяйте значения сами вonSubmit
Вид ошибки включается явно и никогда не выводится из валидности. Передайте error корню поля (TextField, TextArea, NumberField, PinField) и HelperText, чтобы переключить их на цвета ошибки. Библиотека не ставит ARIA за вас: задайте aria-invalid на поле и укажите в aria-describedby тот HelperText, который несёт сообщение.
import { Button, HelperText, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';
const getMessage = (input: HTMLInputElement) => {
if (input.validity.valueMissing) return 'Enter a postal code.';
if (input.validity.patternMismatch) return 'Use 5 digits, for example 10115.';
return '';
};
export default function NativeValidityMessage() {
const id = useId();
const [error, setError] = useState('');Элементы без нативного ограничения, например Select без выбора, проверяются в onSubmit: храните ошибку в состоянии, ставьте aria-invalid на SelectCombobox и переводите на него фокус. См. обязательный выбор в Select.
Отключение группы
Fieldset рендерит нативный <fieldset>, поэтому disabled на нём отключает все элементы внутри по правилам браузера: поля перестают принимать фокус и ввод, а отключённые элементы не попадают в FormData. Объясните причину рядом с группой через HelperText, а не отключайте её молча.
import { useState } from 'react';
import {
Checkbox,
CheckboxCheckedIcon,
CheckboxIndeterminateIcon,
CheckboxIndicator,
CheckboxInput,
Fieldset,
FieldsetLegend,
HelperText,
} from '@vega-ui/react';
Сброс
<button type='reset'> возвращает каждое неконтролируемое поле к значению по умолчанию:
- нативные части возвращаются к
defaultValueилиdefaultChecked NumberField,PhoneField,PinField,DateField,DateRangeField,DateTimeField,TimeFieldиSliderвосстанавливаютdefaultValueсвоего корня, в том числе для формы, связанной атрибутомform- контролируемое поле сохраняет свой
value, потому что им владеет родитель: сбросьте это состояние вonResetформы
В onReset же удобно очищать результаты отправки и сообщения об ошибках, которые хранятся в состоянии.
Чек-лист
- Дайте
nameкаждому отправляемому полю; поле без него не попадает вFormData. - Держите правила валидации на частях-полях, чтобы браузер, автозаполнение и сервер видели одни и те же ограничения.
- Связывайте
aria-invalidиaria-describedbyсами, аerrorпередавайте только как визуальное состояние. - Отключайте группу через
Fieldsetсdisabled, а не черезdisabledна каждом элементе. - Проверяйте те же правила на сервере: валидация на клиенте — удобство, а не гарантия.