---
name: form
category: inputs
summary: "Form/field orchestrator with validation and ARIA wiring"
subpath: "@42/core/form"
deepDoc: docs/llm/form.md
---

Form/field orchestrator. Validates fields, wires ARIA, reflects `data-state` — no styling. Coordinates native inputs and other `@42/core` controls.

```html
<form data-c42-form>
  <div data-c42-field>
    <label for="email">Email</label>
    <input id="email" name="email" type="email" data-c42-validate="required email" />
    <span data-c42-field-error hidden></span>
  </div>
  <div data-c42-field>
    <label for="pwd">Password</label>
    <input id="pwd" name="pwd" type="password" data-c42-validate="required" data-c42-minlength="8" />
    <span data-c42-field-error hidden></span>
  </div>
  <button type="submit">Sign up</button>
</form>
```

```ts
import { Form } from '@42/core/form';
const form = new Form(root, {
  mode: 'blur', // 'submit' (default) | 'blur' | 'change' | 'input'
  validators: { pwd: (v) => (v.length >= 8 ? null : 'Too short') },
  messages: { required: 'Required field' },
});
form.on('form:submit', (e) => console.log(e.detail.values));
form.on('form:invalid', (e) => console.log(e.detail.errors));
```

Discovers fields via `[data-c42-field]` (control = `[data-c42-field-control]` or the
first native `input/select/textarea`; field name = the control's `name`). Built-in
rules in `data-c42-validate`: `required email url number integer`, plus constraint
attrs `data-c42-minlength|maxlength|min|max|pattern` (native `minlength`/`pattern`/…
also read). Per-field message override: `data-c42-error-<rule>`. Radio groups and
checkboxes are supported (unchecked = empty → fails `required`).

Wiring: sets `aria-invalid`, links `[data-c42-field-error]` via `aria-describedby`,
toggles the error's `hidden`, and `data-state="valid|invalid"` on the wrapper. On a
`<form>` root sets `noValidate` and `preventDefault`s submit. After the first submit
attempt, fields re-validate on `input` to clear errors live.

Options: `mode`, `validators`, `messages`
Methods: `validate()`, `validateField(name)`, `submit()`, `getValues()`, `setValues(map)`, `setError(name, msg)`, `clearErrors()`, `reset()`, `getState()`
Events: `form:submit` → `{ values }`, `form:invalid` → `{ errors }`, `form:change` → `{ name, value, values }`, `form:reset`

