/// Inputs
Form
Headless form-state hook (useForm) plus a lightweight Form context that wires labels, controls, and per-field errors.
import { Form, useForm } from "@protocore/pds";Two layers
The form system is deliberately split. `useForm` is a small, dependency-free hook that owns state — values, errors, touched, dirty — and exposes setValue, reset, validate, getInputProps(name), and an onSubmit(handler) wrapper that gates on validation. `Form.Root` / `Form.Field` are the thin display layer: they put a useForm instance on context and render a labelled, error-aware wrapper around each control. Reach for the hook alone when you want full control of the markup; reach for the components when you want the wiring done for you.
Basics
Create an instance with useForm({ initialValues, rules }), hand it to Form.Root, and wrap each control in a Form.Field with a name. The field injects value/onChange/onBlur/id and surfaces the field's error once it has been touched.
Validation on submit
onSubmit runs validation first and only calls your handler when the form is valid; otherwise every field is marked touched and its error appears. Rules are per-field functions returning a message string (or nothing when valid).
Headless — hook only
Skip the components entirely: spread getInputProps(name) onto your own controls and read values / dirty directly. The hook never renders anything.
Value: — · dirty: false
When to use it
Use useForm for local form state — a login box, a settings panel, a create dialog — where you want validation and dirty-tracking without a data-layer dependency. It is intentionally not a form engine: no field arrays, no async field validators, no schema resolver. For those, wire a dedicated library to your controls with getInputProps as the seam, or validate against a schema inside a single validate function.
Usage
Do
- Keep `initialValues` covering every field so `dirty` and `reset` behave.
- Return a message string from a rule when invalid, and a falsy value when valid.
- Let `onSubmit` gate the network call — it only fires the handler when valid.
- Give each `Form.Field` a stable `name` that exists in the values.
Don't
- Don't reach for a heavy form library when a page has one small form.
- Don't duplicate validation in the control and the rule — keep it in the rule.
- Don't read `errors` for display before a field is touched — use `Form.Field`, which waits.
- Don't mutate `values` directly — go through `setValue` / `setValues`.
Accessibility
- `Form.Field` links its label to the control via `htmlFor`/`id` and points `aria-describedby` at the error.
- Field errors render in a `role="alert"` region so they are announced when they appear.
- The `<form>` uses `noValidate` so validation messaging is the component's, not the browser's.