Field
Register one control with its label, instructions, validity and correction messages.
Choose a field composition
Field groups one registered control and its supporting content. Fieldset groups related fields; Form coordinates registered validation and external errors. A label above an unregistered Input may look correct while validation and descriptions remain disconnected.
The installed package is @base-ui-components/react 1.0.0-rc.0. Current upstream docs use a newer package and API. This page describes the installed exports; do not copy newer actionsRef or Field.Item APIs that this wrapper does not export.
Anatomy and specifications
| Export | Actual element and responsibility |
|---|---|
| Field | div, column, 6px gap; field name, validation mode and state |
| FieldLabel | label, text-sm / 600; associates with registered control, or explicit htmlFor for a manually connected native control |
| FieldControl | Installed input primitive; HTMLInputElement ref. Use render={<Input />} for this library’s native appearance. |
| FieldDescription | p, text-sm muted foreground; registered description connection |
| FieldError | div, text-sm error foreground; shows matched constraint/custom/external validation message and connects to registered control |
| FieldValidity | Render function exposing state/error/errors/value/initialValue; no visual shell or live-region policy |
Working registered validation
No registered local value submitted.
'use client';
import { useState } from 'react';
import { Button, Field, FieldControl, FieldDescription, FieldError, FieldLabel, FieldValidity, Fieldset, FieldsetLegend, Form, Input } from '@mobtranslate/ui';
export function RegisteredFieldExample() {
const [disabled, setDisabled] = useState(false);
const [result, setResult] = useState('No registered local value submitted.');
return <div data-field-lab className="space-y-4">
<Form onFormSubmit={values => setResult(`Registered local value: ${String(values.title ?? 'omitted')}. No data was sent.`)}>
<Field name="title" disabled={disabled} validate={value => typeof value === 'string' && value.trim().length >= 3 ? null : 'Use at least three non-space code units for the local title.'} className={state => state.valid === false ? 'local-field-invalid' : undefined}>
<FieldLabel>Local example title (required)</FieldLabel>
<FieldControl data-registered-title required render={<Input />} />
<FieldDescription>This control registers its value and description with Field.</FieldDescription>
<FieldError />
<FieldValidity>{validity => <p data-field-validity>Validation: {String(validity.validity.valid)}; current value: {String(validity.value ?? '')}.</p>}</FieldValidity>
</Field>
<Button type="submit">Submit registered field</Button>
</Form>
<Button type="button" variant="outline" onClick={() => setDisabled(current => !current)}>{disabled ? 'Enable registered field' : 'Disable registered field'}</Button>
<p role="status">{result}</p>
</div>;
}
export function FieldsetInputExample() {
const [disabled, setDisabled] = useState(false);
const [result, setResult] = useState('No grouped native payload inspected.');
return <form data-fieldset-lab className="space-y-4" onSubmit={event => {
event.preventDefault(); const data = new FormData(event.currentTarget);
setResult(`Native payload: ${JSON.stringify(Object.fromEntries(data))}. No data was sent.`);
}}>
<Fieldset disabled={disabled} className={state => state.disabled ? 'local-fieldset-disabled' : undefined}>
<FieldsetLegend>Local example details</FieldsetLegend>
<div className="space-y-4"><label className="block" htmlFor="group-title">Local title</label><Input id="group-title" name="groupTitle" defaultValue="Reading sample" /><label className="block" htmlFor="group-note">Local reference</label><Input id="group-note" name="groupReference" defaultValue="Example only" /></div>
</Fieldset>
<div className="flex flex-wrap gap-3"><Button type="button" variant="outline" onClick={() => setDisabled(current => !current)}>{disabled ? 'Enable grouped details' : 'Disable grouped details'}</Button><Button type="submit">Inspect grouped payload</Button></div>
<p role="status">{result}</p>
</form>;
}
Root and part API
| Prop / part | Installed contract |
|---|---|
| Field name / disabled | Name takes precedence over FieldControl name; root disabled takes precedence over control disabled. |
| validate | (value, formValues) => string|string[]|null, optionally Promise. Async validation does not block an onSubmit form; explicitly await authoritative checks in your task. |
| validationMode / validationDebounceTime | onSubmit default; onBlur|onChange alternatives. Debounce applies to onChange custom validation; avoid duplicate stale requests. |
| invalid / dirty / touched | Optional externally controlled state. Externally marking invalid still needs a correction message. |
| FieldControl value / defaultValue / onValueChange | Input ownership; callback gives string and change details. Native constraints, required and readOnly are available. |
| FieldError match | true always shows; validity key such as valueMissing/typeMismatch/customError selects a rule. Default shows current validation errors. |
| FieldValidity children | Function of installed validity data: validity, error, errors, value and initialValue. |
| className / style / render / native props | Installed Base UI contracts. State callbacks are preserved by styled wrappers. Custom elements must retain semantics, handlers and refs. |
Validation timing, keyboard and state
- onSubmit avoids errors before the user attempts the task and then revalidates changed values. onBlur can support correction; onChange must tolerate incomplete editing/composition.
- The label focuses the registered control. Form directs failed submission to an invalid registered control; describe the correction and retain other values. Verify actual linked IDs in the browser.
- Empty, filled, focused, touched, dirty, valid and invalid are separate states. A field can be filled and invalid; a disabled field is not a successful save.
- Use custom validation for actual business rules and server errors keyed by the same field name. Do not treat async validation as proof that submission waited.
Failures and recovery
- Replace the old plain Input nesting with FieldControl render={<Input />}, or manually own every native connection and validation rule. Styling does not register a control.
- Do not stack duplicate assertive errors or hide errors solely in a color. Keep help available and verify aria-describedby resolves to real elements.
- Native disabled FormData and the Form registered-value object are different models. Inspect the payload your application actually uses.
Verification and release scope
- Verify the actual label/control connection, description/error IDs, invalid state, focus order and corrected input with pointer and keyboard. Read the submitted payload rather than inferring it from visible text.
- Test blank, populated, invalid, disabled, read-only and pending/recovery states. Check both themes, long labels, user text spacing, 320px reflow, reduced motion, forced colors and zoom.
- Run the named input-family browser checks and source/type/build checks. Inspect mobile keyboards, autofill, password managers and a named screen-reader/browser pair separately. DOM tests do not prove their behavior.
- Client validation supports correction; it does not establish authorization, consent, source authority, language quality, identity, security or a successful server write.