Form
formCoordinates named fields with structured submission and field errors.
Usage
Basic usage
Base UI Form container, you get the structured values (preventDefault) when submitting.
<Form className="w-72" onFormSubmit={(v) => console.log(v)}>
<Field label="Email" name="email">
<Input name="email" type="email" placeholder="you@example.com" required />
</Field>
<Field label="Username" name="username">
<Input name="username" placeholder="Hulian user" required />
</Field>
<Button type="submit" size="sm">Submit</Button>
</Form>Server error
errors According to Field name mapping, the external/server verification results are echoed to the corresponding fields.
<Form
className="w-72"
errors={{ email: "This email address has been registered" }}
onFormSubmit={(v) => console.log(v)}
>
<Field label="Email" name="email">
<Input name="email" type="email" defaultValue="taken@example.com" />
</Field>
<Button type="submit" size="sm">Submit</Button>
</Form>Verification rules + field linkage (useForm)
useForm controller: register declares rules, dependencies does field linkage, and submit packages are submitted.
const form = useForm({ initialValues: { email: "", pwd: "", confirm: "" } });
const email = form.register("email", {
rules: [
{ required: true, message: "Please fill in your email address" },
{ pattern: /^[^@]+@[^@]+$/, message: "The email format is incorrect" },
],
});
const pwd = form.register("pwd", { rules: [{ required: true, min: 6, message: "At least 6 digits" }] });
const confirm = form.register("confirm", {
dependencies: ["pwd"],
rules: [{ validator: (v, values) => { if (v !== values.pwd) throw new Error("Two passwords are inconsistent"); } }],
When
});
<form onSubmit={form.submit(onFinish, onFinishFailed)} noValidate>
<Field label="Email" error={email.error}>
<Input value={email.value} onChange={email.onChange} onBlur={email.onBlur} />
</Field>
<Field label="Password" error={pwd.error}>
<Input type="password" value={pwd.value} onChange={pwd.onChange} onBlur={pwd.onBlur} />
</Field>
<Field label="Confirm password" error={confirm.error}>
<Input type="password" value={confirm.value} onChange={confirm.onChange} onBlur={confirm.onBlur} />
</Field>
<Button type="submit" size="sm">Submit</Button>
</form>Dynamic list (FormList)
FormList manages repeating field rows that can be added, deleted, and moved.
<FormList<{ name: string }> value={rows} onChange={setRows}>
{(fields, ops, value) => (
<>
{fields.map((f) => (
<div key={f.key} className="flex items-center gap-2">
<Input value={value[f.name]?.name ?? ""} onChange={...} />
<Button onClick={() => ops.remove(f.name)}>Delete</Button>
</div>
))}
<Button onClick={() => ops.add({ name: "" })}>+ Add Contact</Button>
</>
)}
</FormList>When to use
Use Form for a group of fields that submits structured values and owns its validation flow. Form collects values by native field name, useForm adds registration, rules, dependencies, and controlled submission, and FormList manages dynamic rows. Use ModalForm / DrawerForm for overlays, ProForm for an inline grid with a standard footer, or SearchForm for list filters.
Import
import { Form, useForm, validateValue, FormList } from "@hulianui/ui"Props
Form:
| Name | Type | Default | Description |
|---|---|---|---|
| validationMode | "onSubmit" | "onBlur" | "onChange" | "onSubmit" | Point at which validation runs. |
| errors | Record<string, string | string[]> | - | External or server errors keyed by <Field name>. The matching Field must render Field.Error. |
| className | string | - | Additional class name for the form container. |
Events
| Event | Type | Description |
|---|---|---|
| onFormSubmit | (formValues: Record<string, unknown>) => void | Called with structured values after the component prevents native form submission. |
Slots
| Slot | Type | Description |
|---|---|---|
| children | ReactNode | Fields and form actions. |
useForm controller usage (see examples for details): form.register(name, { rules, dependencies }), form.submit(onValid, onInvalid), form.resetFields().
register() returns { name, value, onChange, onBlur, error, required }. The required flag is derived from whether the rules contain required: true; forward it to the required prop of `Field` so the requirement is visible before submitting (asterisk plus aria-required), while the rules remain the only source of validation.
How the binding reports emptiness (null and undefined are not the same)
register(name).value mirrors `form.values[name]` as-is, with one exception: a field that never received an initial value (undefined) reports "", because handing undefined to a controlled control makes React treat it as uncontrolled and the first keystroke then triggers the "uncontrolled to controlled" warning.
null passes through (#220). It is the business value "explicitly cleared / left blank", a step the user picked just like 0 or "", so three-state fields work:
// null inherits the global setting - 0 is an explicit zero - a positive integer overrides
const form = useForm({ initialValues: { bonus: null as number | null } });
const bonus = form.register("bonus");
<NumberField
value={bonus.value as number | null}
onValueChange={bonus.onChange}
aria-label="Points"
/>
<Button variant="link" size="xs" muted onClick={() => bonus.onChange(null)}>Clear (inherit global)</Button>That was not true in 0.37.0 and earlier: the binding read values[name] ?? "", so within one render form.values.bonus was null while bonus.value was "" - two answers to the same question. Patching it downstream with ?? null does not help either (?? only fires on null and undefined, an empty string sails right through), so the control ends up with an out-of-signature "" - and `NumberField` renders that as 0, while "left blank" and "explicitly zero" are opposite business conclusions.
One boundary: when spreading the binding onto a native <input> or <textarea>, write value={v ?? ""} yourself, otherwise React logs "value prop should not be null". Hulian's `Input` and `Textarea` already fold null into an empty string, so they can take it directly.
Usage guidelines
- A
validatorreports failure withthrow new Error("..."), notfalse. Declare linked fields independenciesso validation reruns when they change. - To display external or server
errors, renderField.Errorin the matching Field and make each error key exactly match<Field name>. - With
useForm, addnoValidateto the native<form>so browser validation does not preempt the rule engine.
Related
ModalForm / DrawerForm · ProForm · StepsForm · LoginForm · Field · SearchForm