Form Engine Overview
Defining form fields, built-in field types, and the field-binding contract every field component receives.
The form engine
Prism's form engine is three functions and one component, spread across two packages:
defineRegistryandcreateFormBuilder(@prismrepo/forms), declaring what field types exist and what fields a form has.usePrismFormandPrismField(@prismrepo/adapter-rhf), building a real React Hook Form instance and rendering a configured field.
Defining form fields
A form's fields are declared once, as a configuration object passed to defineForm:
const definition = createFormBuilder(registry).defineForm<FormValues>({
name: { type: 'text', label: 'Name' },
email: { type: 'text', label: 'Email' },
age: { type: 'number', label: 'Age' },
});Each key must match a key of FormValues. Each value is a discriminated union over every field
type in the registry whose component accepts exactly that key's value type, { type: <name> }
plus whatever extra props that field component declares.
Type-checked field configuration
defineForm<TValues> checks, at compile time:
- That every configured field's
typeexists in the registry. - That every required extra prop the field component declares is present.
- That the field component's declared value type exactly matches
TValues[K]for that field, not merely an assignable one. Astring-only field component cannot be configured against anumber-typed form field, and vice versa, this is Prism's standing type-safety guarantee.
At runtime, defineForm also defensively checks that every field's type is actually a
registry key, throwing PrismConfigurationError if not. This matters when fields was built
from data the type checker couldn't see, for example parsed JSON, rather than a literal object.
Built-in field types
Prism itself ships no field components as package exports, see Customization Model. The CLI's catalog installs five Layer 2 field components, each wrapping a Layer 1 primitive:
| Catalog item | Wraps | Value type |
|---|---|---|
text-field | Input | string |
number-field | Input (type="number") | number |
select-field | Select | string |
checkbox-field | Checkbox | boolean |
date-field | Input (type="date") | string (ISO date) |
These are real, installable templates, not package exports, see Project Setup to install them. You are never limited to this catalog, see Custom Fields.
The field-binding contract
Every field component, whether from Prism's catalog or written by you, receives the same normalized contract:
interface FieldBinding<TValue> {
readonly name: string;
readonly value: TValue;
readonly onChange: (value: TValue) => void;
readonly onBlur: () => void;
readonly error: string | undefined;
}
type FieldComponentProps<TValue, TExtraProps extends object = object> = TExtraProps & {
readonly field: FieldBinding<TValue>;
};<PrismField> constructs a real FieldBinding from React Hook Form's Controller at render
time. A field component never imports react-hook-form itself, it only ever sees this
normalized shape, which is what lets the same field component wrap any UI library.