Aura Form System
Form system — useFormDynamic, Form components, validation (validateFormData), and field composition.
Aura Form System
Installation
pnpm dlx shadcn@latest add @aura/rule-components-formsForms use useFormDynamic for state, Form for error distribution, and validateFormData (from @/utils/web-validation) for JSON Schema validation. Schema property names must match field names so errors match by instancePath.
Hook & state
- MUST: Define forms with
useFormDynamic(initialValues, formRef?)whereinitialValuesisRecord<string, FieldType>andFieldTypeis"text" | "textarea" | "select" | "checkbox". - MUST: Get field props via
formData.getFields()(e.g.const { name, email } = formData.getFields()) orformData.field(name)and pass them as thefieldprop to Form field components. - MUST: Use
formData.getValues()for validation and submit payloads.
Form root & errors
- MUST: Wrap fields in
<Form ref={formRef} onSubmit={...} errors={formErrors} id="form-id">. Do not passerrorsto each field; Form injects them by matchingerror.instancePath.slice(1)tofield.name. - MUST: On failed validation call
formData.touchForm()so field-level errors show, then passerrors ?? undefinedtoForm. - SHOULD: Use
FormAlert formData={{ fetchStatus: formData.fetchStatus, error: formData.error }}for form-level errors whenfetchStatus === "error".
Validation
- MUST: Validate with
validateFormData(schema, formData.getValues())from@/utils/web-validation. Use the returnederrors(AJVErrorObject[]) as theerrorsprop on Form. - MUST: Show field errors only when
field.touch && errors?.length > 0; Form field components do this viaserverInvalidandFormRadix.Message.
Field components
- MUST: Use FormField for native controls (input, textarea, select) with
field.onChange; use FormSwitch / FormCheckbox withfield.valueandfield.onCheckedChange. - MUST: Use FormCheckboxGroup / FormRadioGroup with
field.setValue(array or string). For composed controls (Select, Combobox, Editor, SignaturePad, SortableList), usefield.setValue(value)in the control’s change handler; do not passfield.valueas controlled value for Select (uncontrolled +onValueChange→field.setValue). - MUST: Use FormSubmit with
form={id}matching Form’sidandfetchStatus={formData.fetchStatus}for loading UI.
Submit flow
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
formData.setFetchStatus("loading");
const { isValid, errors } = validateFormData(schema, formData.getValues());
if (!isValid) {
formData.setFetchStatus("error");
formData.touchForm();
formData.setError("Please fix the errors below.");
return;
}
// submit...
formData.setFetchStatus("success");
};Implementation
// Correct: field from hook, errors from Form, validate with getValues()
const formData = useFormDynamic({ name: "text", email: "text" }, formRef);
const { name, email } = formData.getFields();
const { isValid, errors } = validateFormData(schema, formData.getValues());
<Form ref={formRef} onSubmit={handleSubmit} errors={errors ?? undefined} id="my-form">
<FormAlert formData={{ fetchStatus: formData.fetchStatus, error: formData.error }} />
<FormField field={name} label="Name *"><Input type="text" /></FormField>
<FormField field={email} label="Email *"><Input type="email" /></FormField>
<FormSubmit fetchStatus={formData.fetchStatus} buttonProps={{ children: "Submit" }} form="my-form" />
</Form>