Skip to content
v2.5.0
English
Русский
Documentation

Forms and validation

Vega UI fields are native form controls, so a plain <form> submits them, validates them and resets them without extra state.

Native Inputs Do The Work

Every field part renders a real element: TextFieldInput, PasswordFieldInput, NumberFieldInput, PhoneFieldInput, DateFieldInput and TimeFieldInput are <input>, TextArea is a <textarea>, Radio is an <input type="radio">, and CheckboxInput, SwitchInput, SegmentedControlItemInput and SliderThumbInput are the inputs inside their components. PinField keeps the code in one visually hidden input behind its slots.

That gives forms the browser behavior for free:

  • name, value, required, pattern, min, max, minLength and autoComplete go on the input part, exactly as on the native element
  • new FormData(form) reads the values, and a submit without preventDefault() posts them like any HTML form
  • Button has no default type, so inside a form it submits like a native <button>; pass type='button' for buttons that should not
  • the browser autofill, password managers and the form attribute work on the inputs as usual
profile-form-submit.tsx · 39 lines
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')}>`);
  };

What Each Field Submits

A native input submits the text it shows. For most fields that is the value itself; a few components keep a different value on the root:

ComponentSubmitted with name on the inputValue on the root
TextField, TextArea, PasswordFieldthe typed text—
NumberFieldthe formatted text, for example 2 500value / defaultValue as a number, NaN when empty
DateField, DateRangeField, DateTimeField, TimeFieldthe typed text in format, for example 15.01.2026ISO value / defaultValue, see Dates and calendar systems
Checkbox, Radio, Switch, SegmentedControlvalue of each checked input—
Slidereach thumb value; name on the root applies to every thumbvalue / defaultValue as an array
Selectthe selected value, through SelectHiddenSelect with a namevalue / defaultValue

When the server expects the root value rather than the shown text, mirror it into a hidden input from onChangeValue. The DateField example submits the ISO day this way. Listbox has no form part at all: render one hidden input per selected value, as in Listbox in a form.

native-form-participation.tsx · 68 lines
import {
  Button,
  Label,
  Select,
  SelectCombobox,
  SelectHiddenSelect,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
  Text,
} from '@vega-ui/react';

Constraint Validation

required, pattern, type='email', min and max on the input parts block the submit and fill input.validity, the same as on native inputs. The browser shows its own message bubble unless you take over:

  • listen to onInvalid on the input and call event.preventDefault() to hide the bubble, then read event.currentTarget.validity to pick your message
  • or put noValidate on the form and check the values yourself in onSubmit

The error look is opt-in and never derived from validity. Pass error to the field root (TextField, TextArea, NumberField, PinField) and to HelperText to switch them to the error colors. The library does not set ARIA for you: set aria-invalid on the input and point aria-describedby at the HelperText that carries the message.

native-validity-message.tsx · 49 lines
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('');

Controls without a native constraint, such as a Select without a choice, are validated in onSubmit: keep the error in state, set aria-invalid on SelectCombobox, and move focus to it. See Select required error.

Disabling A Group

Fieldset renders a native <fieldset>, so disabled on it disables every control inside by browser rules: the inputs stop taking focus and input, and disabled controls are left out of FormData. Put a reason next to the group with HelperText instead of disabling silently.

disabled-by-fieldset.tsx · 60 lines
import { useState } from 'react';
import {
  Checkbox,
  CheckboxCheckedIcon,
  CheckboxIndeterminateIcon,
  CheckboxIndicator,
  CheckboxInput,
  Fieldset,
  FieldsetLegend,
  HelperText,
} from '@vega-ui/react';

Reset

A <button type='reset'> restores every uncontrolled field to its default:

  • native parts return to defaultValue or defaultChecked
  • NumberField, PhoneField, PinField, DateField, DateRangeField, DateTimeField, TimeField and Slider restore the defaultValue of their root, also for a form linked with the form attribute
  • a controlled field keeps its value, because the parent owns it: reset that state in onReset on the form

onReset is also the place to clear submit results and error messages kept in state.

Checklist

  • Give every submitted input a name; a field without one is not in FormData.
  • Keep validation rules on the input parts, so the browser, autofill and the server see the same constraints.
  • Wire aria-invalid and aria-describedby yourself, and pass error only as the visual state.
  • Use Fieldset with disabled for a group, not disabled on each control.
  • Check the same rules on the server: client validation is a convenience, not a guarantee.