Skip to Content
Prism UI
Core Concepts

Configuration-Driven Design

Why a Prism form is declared as data, and what that buys you.

Declaring a form as data

A typical hand-rolled React Hook Form integration assembles a form by writing one Controller (or register call) per field, by hand, in JSX. Prism inverts this: you declare which fields exist and what type each one is, once, as a plain configuration object:

const definition = createFormBuilder(registry).defineForm<SignupValues>({
  name: { type: 'text', label: 'Full name' },
  email: { type: 'text', label: 'Email' },
  age: { type: 'number', label: 'Age' },
});

<PrismField form={form} name="name" /> then renders whatever defineForm configured for name, resolved against the registry at render time. Adding a field means adding one entry to this object, not writing a new block of JSX.

What this buys you

  • One source of truth per form. The field list, its types, and its extra props live in one object, not scattered across JSX.
  • Compile-time safety a hand-rolled integration can't get for free. createFormBuilder(registry).defineForm<TValues> checks that every configured field's type exists in the registry, that its component's declared value type exactly matches TValues[K], and that every required extra prop is present. A string-valued field component cannot be configured against a number field, see Forms.
  • Static configuration, kept separate from runtime state. A FormDefinition is immutable once created (ADR-0013); the actual, changing form state (values, dirty flags, errors) lives entirely in React Hook Form, which Prism never duplicates or shadows.

What it deliberately doesn't do

Configuration-driven doesn't mean "no imperative escape hatch." A field's configuration is static, fixed once in defineForm, there's no way to pass different props to a field on a given render based on another field's current value. For genuinely dynamic cases, Prism's answer is to drop to React Hook Form's own Controller directly, reachable via form.control since usePrismForm returns the complete, real UseFormReturn, see Advanced Forms Usage. This is not a gap to be papered over with a dynamic-configuration API later, it is the architecture's "preserve full RHF access" principle working as intended.

On this page