Your first form
A FormConfig is a plain object — no builder, no schema file to hand-write separately. This one has two fields and a submit button.
1. Write the config
import type { FormConfig } from "@/form-builder";
export const firstFormConfig: FormConfig = {
id: "your-first-form",
fields: [
{ type: "text", name: "name", label: "Name", required: true },
{ type: "email", name: "email", label: "Email", required: true },
{ type: "submit", name: "submit", text: "Submit" },
],
};2. Render it
Pass the config to FormRenderer with an onSubmit. Nothing is sent anywhere by the engine itself — you own what happens with the values.
import { FormRenderer } from "@/form-builder";
import { firstFormConfig } from "./config";
export function SignupForm() {
return (
<FormRenderer
config={firstFormConfig}
onSubmit={(values) => {
// values: { name: string; email: string }
console.log(values);
}}
/>
);
}3. Try it
This is the exact config above, rendered by the real FormRenderer — leave a field blank and submit to see validation kick in.
View config
{
"id": "your-first-form",
"fields": [
{
"type": "text",
"name": "name",
"label": "Name",
"required": true
},
{
"type": "email",
"name": "email",
"label": "Email",
"required": true
},
{
"type": "submit",
"name": "submit",
"text": "Submit"
}
]
}What you got for free
- Zod validation— a schema is derived from the config (each field's
required, type, and rules), never hand-duplicated in a separate schema file. The email field is checked for email format automatically. - Error messages — human-readable, out of the box, wired to each field via
aria-describedbyfor screen readers. Override any of them with amessagesprop onFormRenderer. - Grid layout — every field defaults to a full-width row in a 12-column responsive grid. Set
width: "half"(or a per-breakpoint object) on a field to change that.
Conditional fields, multi-step wizards, and cross-field rules aren't covered here — see Examples for those working live. For the full list of field types, see Field types.