Forms
Every control submits like a native input.
Give a control a name and it submits through FormData, the shape a plain form, Inertia <Form>, React Router actions, and server actions all read. No onChange wiring, no hidden-input glue.
Native submission for DatePicker, DateRangePicker, TagInput, FileUpload, Dropdown, and Rating, plus parseDateOnly, needs @dev-dga/react 1.4.0 or later.
What each control submits
The rules match native inputs: a disabled control submits nothing, required blocks submission while the control is empty, and a form reset restores the initial value.
| Control | Submits | Notes |
|---|---|---|
TextInput, Textarea, SearchBox | The text | Native input; every input prop passes through. |
NumberInput | Plain decimal string, such as "1234.5" | No digit grouping, always Latin digits. Empty submits "". |
Select | The selected value | Empty submits "". |
Dropdown | The selected value | With multiple, one entry per value under the same name. Empty single submits "". |
Checkbox | value, or "on" | Unchecked submits nothing. Give a group one name and read it with getAll. |
RadioGroup | The selected value | No selection submits nothing. |
Switch | "on" | Off submits nothing. |
Slider | The value | A range submits name[] once per thumb; Radix appends the brackets. |
Rating | Number string, such as "4" or "3.5" | Unrated submits "0". |
TagInput | One entry per tag | No tags submits nothing. The uncommitted draft text never submits. |
InputOTP | The entered code | Native input under the slots. |
DatePicker | Gregorian YYYY-MM-DD | Even while Hijri is displayed. Empty submits "". |
DateRangePicker | Gregorian YYYY-MM-DD under startName and endName | Each end submits separately. |
FileUpload | Every listed file (multipart) | The native input mirrors files, dropped ones included. A reset leaves files to you. |
ContentSwitcher, Toggle | Nothing | View switchers, not form controls. For a form choice, use RadioGroup or Checkbox. |
Multiple values
TagInput, Dropdown with multiple, and Checkbox groups submit one FormData entry per value under the exact name you pass, like a native <select multiple>. Read them with formData.getAll(name). PHP and Laravel only build an array from names ending in [], so pass name="tags[]" there.
Plain forms
Wrap the controls in a <form> and read FormData in the submit handler. This is the contract every integration below builds on.
Inertia <Form>
Inertia builds its payload from new FormData(form), so named controls need no extra wiring. Its errors map straight onto each control’s error and errorMessage props.
A full reset() fires a reset event that the controls listen for. A per-field reset, such as resetOnSuccess={["name"]}, fires no event, so controls that keep their own state (date pickers, TagInput, Dropdown, Rating, NumberInput, InputOTP) keep their current value. Reset the whole form, or control those fields yourself.
React Router actions
React Router’s <Form> posts the same FormData to the route action. Read it with request.formData() and return validation errors from the action.
Server actions
Pass a server action to the form’s action prop. useActionState returns the action’s result, so the server’s field errors flow back into the controls.
Forms in a Modal
ModalContent takes title and footer props, so the submit button sits in the footer, outside the <form>. Three rules keep that working:
- Give the form an id and the footer button form={formId}. The button submits that form, and Enter in a field submits through it.
- Pass the pending state to the button’s loading prop. A loading button is disabled, so a second click or Enter can’t send the form twice.
- Show a server error that belongs to no field in an InlineAlert above the fields, and each field error on its field.
Inertia <Form>
Server action
The pending state lives outside the form because the button does. Close the Modal on success: Inertia calls onSuccess only when the server returns no errors. The title, description, and footer props need @dev-dga/react 1.6.0 or later.
Server validation errors
Every field takes error (the invalid state) and errorMessage (the text below the field). Together they set aria-invalid and point aria-describedby at the message, so screen readers announce the server’s message with the field.
Set error and errorMessage together. errorMessage without error stays hidden, and error alone marks the field without telling the user why.
Dates from the server
Servers send dates as YYYY-MM-DD strings. Convert them with parseDateOnly, which builds a local-midnight Date and throws a RangeError on any other shape. new Date("2026-10-20") parses as UTC, so anywhere west of UTC it shows October 19.