Foundation
Form uses TanStack Form. Keep MiHCM form documentation, examples, and agent rules on this path; do not introduce React Hook Form for the design-system form layer.
Use cases
- Complete form experiences — login, registration, contact, settings.
- Forms that need field-level validation (sync and async).
- Multi-step wizards with shared form state across steps.
- Dynamic field arrays (add/remove rows at runtime).
When NOT to use
- Single uncontrolled input — use
Inputdirectly with native form submission. - Read-only data display — use
CardorTable. - Search — use
SearchFieldwhich handles debounce and clear internally.
Anatomy
Each integrated field (FormInput, FormSelect, etc.) renders a FormItem wrapper containing a FormLabel, the control, an optional FormDescription, and a FormMessage for errors.
┌ FormItem ─────────────────────────────────┐
│ FormLabel (with optional * for required) │
│ [Control: Input / Select / ...] │
│ FormDescription (muted helper text) │
│ FormMessage (error with warning icon) │
└───────────────────────────────────────────┘
Quick start
import { useForm, FormInput } from '@mihcm/ui/Form';
import { Button } from '@mihcm/ui/Button';
function MyForm() {
const form = useForm({
defaultValues: { email: '' },
onSubmit: async ({ value }) => console.log(value),
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<FormInput
form={form}
name="email"
label="Email"
type="email"
required
validators={{
onChange: ({ value }) =>
!value.includes('@') ? 'Invalid email' : undefined,
}}
/>
<Button type="submit">Submit</Button>
</form>
);
}Available fields
| Component | Control | Value type |
|---|---|---|
FormInput | Input | string |
FormTextarea | Textarea | string |
FormCheckbox | Checkbox | boolean |
FormSwitch | Switch | boolean |
FormSelect | Select | string |
FormRadioGroup | RadioGroup | string |
Utility components
| Component | Purpose |
|---|---|
FormFieldArray | Manages dynamic add/remove lists via mode="array". Renders each item with a renderField callback. |
FormSubscribe | Reactive state display — wraps form.Subscribe with a selector and render function. |
FormListenEffect | Invisible field that fires side effects when another field changes (e.g. country → reset city). |
FormActions | Submit + reset button footer. Auto-disables based on canSubmit / isSubmitting state. |
FormFieldArray
import { FormFieldArray, FormInput } from '@mihcm/ui/Form';
<FormFieldArray
form={form}
name="members"
label="Team members"
addLabel="Add member"
renderField={({ index, remove }) => (
<div className="flex gap-2">
<FormInput form={form} name={`members[${index}].name`} label="Name" />
<Button variant="outline" size="sm" onClick={remove}>Remove</Button>
</div>
)}
/>FormSubscribe
import { FormSubscribe } from '@mihcm/ui/Form';
<FormSubscribe
form={form}
selector={(state) => state.values}
>
{(values) => <pre>{JSON.stringify(values, null, 2)}</pre>}
</FormSubscribe>FormListenEffect
import { FormListenEffect } from '@mihcm/ui/Form';
<FormListenEffect
form={form}
name="country"
listeners={{
onChange: ({ value }) => {
form.setFieldValue('city', '');
},
}}
/>FormActions
import { FormActions } from '@mihcm/ui/Form';
<FormActions form={form} submitLabel="Save" showReset />Composition
- Form is built on TanStack Form. Define validators in the form config and keep Zod at API, AI descriptor, or host validation boundaries when runtime schemas are required.
- Each
FormFieldwraps a control (Input, Select, Checkbox) with automatic label, error, and description wiring. FormActionsrenders Submit + optional Reset buttons aligned to your layout.- Nest forms inside Dialog or Sheet for modal editing flows.