Skip to Content
Prism UI
Forms

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:

  • defineRegistry and createFormBuilder (@prismrepo/forms), declaring what field types exist and what fields a form has.
  • usePrismForm and PrismField (@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 type exists 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. A string-only field component cannot be configured against a number-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 itemWrapsValue type
text-fieldInputstring
number-fieldInput (type="number")number
select-fieldSelectstring
checkbox-fieldCheckboxboolean
date-fieldInput (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.

Next

On this page