MiHCM
primitives/@mihcm/ui·v0.21.0·stable

Form

Full-featured form system integrated with TanStack React Form v1. Field wrappers include input, search, textarea, checkbox, switch, select, combobox, radio, radio cards, date, date range, checkbox grid, dropzone, field arrays, subscriptions, effects, and actions.

Login form

Email and password with onChange validation. Submit is disabled while the form is invalid using form.Subscribe.

<FormInput
  form={form}
  name="email"
  label="Email"
  type="email"
  validators={{
    onChange: ({ value }) =>
      !value.includes('@') ? 'Must be a valid email' : undefined,
  }}
/>

Contact form

Name, email, subject (FormSelect), and message (FormTextarea). Demonstrates mixing field types in a single form.

<FormSelect
  form={form}
  name="subject"
  label="Subject"
  options={[
    { value: 'general', label: 'General' },
    { value: 'support', label: 'Support' },
  ]}
/>

Settings form

Mixed field types: FormInput for display name, FormTextarea for bio, FormSwitch for notifications, FormRadioGroup for theme, and FormCheckbox for opt-in.

<FormRadioGroup
  form={form}
  name="theme"
  label="Theme"
  options={[
    { value: 'light', label: 'Light' },
    { value: 'dark', label: 'Dark' },
    { value: 'system', label: 'System' },
  ]}
/>

Validation showcase

Demonstrates three validator types on different fields:

  • onChange — synchronous, fires on every keystroke (username min-length, age check).
  • onBlur — fires when the field loses focus (email format).
  • onChangeAsync — async with a simulated server check (username uniqueness).
validators={{
  onChange: ({ value }) =>
    value.length < 3 ? 'Min 3 characters' : undefined,
  onChangeAsync: async ({ value }) => {
    await new Promise((r) => setTimeout(r, 500));
    return value === 'admin' ? 'Taken' : undefined;
  },
}}

Dynamic field array with FormFieldArray

Use the FormFieldArray utility for a cleaner API than raw form.Field with mode="array". It handles add/remove buttons and provides index, total, and remove in the render callback.

<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" />
      <FormSelect
        form={form}
        name={`members[${index}].role`}
        label="Role"
        options={[
          { value: 'dev', label: 'Developer' },
          { value: 'design', label: 'Designer' },
        ]}
      />
      <Button variant="outline" size="sm" onClick={remove}>Remove</Button>
    </div>
  )}
/>

Linked fields with FormListenEffect

When one field changes, reset or update another. FormListenEffect renders nothing — it only fires side effects.

<FormSelect form={form} name="country" label="Country" options={countries} />
<FormSelect form={form} name="city" label="City" options={citiesForCountry} />
<FormListenEffect
  form={form}
  name="country"
  listeners={{
    onChange: ({ value }) => {
      form.setFieldValue('city', '');
    },
  }}
/>

FormActions provides a submit and optional reset button that auto-disables based on form state.

<form onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>
  <FormInput form={form} name="name" label="Name" />
  <FormInput form={form} name="email" label="Email" type="email" />
  <FormActions form={form} submitLabel="Save changes" showReset />
</form>

Live state preview with FormSubscribe

Use FormSubscribe to reactively display form values or derived state.

{
  "name": "",
  "email": ""
}
<FormSubscribe
  form={form}
  selector={(state) => ({ values: state.values, dirty: state.isDirty })}
>
  {({ values, dirty }) => (
    <pre className="text-xs bg-muted p-3 rounded-lg">
      {dirty ? '(modified) ' : ''}{JSON.stringify(values, null, 2)}
    </pre>
  )}
</FormSubscribe>

Employee onboarding (complex multi-section)

A real-world multi-section form with fieldset/legend grouping, two-column grid layouts, and mixed field types. Demonstrates how to compose FormInput, FormSelect, FormRadioGroup, FormTextarea, FormCheckbox, and FormSwitch in a production-like structure.

Personal information

Employment details

Address

Additional
<form className="w-full max-w-2xl space-y-8">
  <fieldset className="space-y-4">
    <legend className="text-sm font-semibold text-foreground mb-2">
      Personal information
    </legend>
    <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
      <FormInput form={form} name="firstName" label="First name" />
      <FormInput form={form} name="lastName" label="Last name" />
    </div>

  </fieldset>
  <hr className="border-border" />
  <fieldset className="space-y-4">
    <legend className="text-sm font-semibold …">Employment details</legend>

  </fieldset>
  <FormActions form={form} submitLabel="Submit application" showReset />
</form>