MobTranslate styleguide

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

ExportActual element and responsibility
Fielddiv, column, 6px gap; field name, validation mode and state
FieldLabellabel, text-sm / 600; associates with registered control, or explicit htmlFor for a manually connected native control
FieldControlInstalled input primitive; HTMLInputElement ref. Use render={<Input />} for this library’s native appearance.
FieldDescriptionp, text-sm muted foreground; registered description connection
FieldErrordiv, text-sm error foreground; shows matched constraint/custom/external validation message and connects to registered control
FieldValidityRender function exposing state/error/errors/value/initialValue; no visual shell or live-region policy

Working registered validation

This control registers its value and description with Field.

Validation: null; current value: .

No registered local value submitted.

tsx
'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 / partInstalled contract
Field name / disabledName 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 / validationDebounceTimeonSubmit default; onBlur|onChange alternatives. Debounce applies to onChange custom validation; avoid duplicate stale requests.
invalid / dirty / touchedOptional externally controlled state. Externally marking invalid still needs a correction message.
FieldControl value / defaultValue / onValueChangeInput ownership; callback gives string and change details. Native constraints, required and readOnly are available.
FieldError matchtrue always shows; validity key such as valueMissing/typeMismatch/customError selects a rule. Default shows current validation errors.
FieldValidity childrenFunction of installed validity data: validity, error, errors, value and initialValue.
className / style / render / native propsInstalled 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.