Ship forms your team actually owns.
A visual builder that exports real, Zod- and React Hook Form–validated React — you own the code, not a hosted widget.
landing-demo
Live demo
See it running
ConditionsConditional profileThree fields wired to each other: visibleWhen, optionsFrom, countryFrom.
Field typesAdvanced fieldsMasked input, sibling-bound dates, signature capture — still just field configs.
BuilderVisual builderDrag fields, wire conditions, hit export. Same FormConfig shape as everything else here.
One config. Two views.
This form. That code. No translation layer.
signup.config.tspreview
Config source
import { defineForm, type InferValues } from "@/form-builder";
export const config = defineForm({
"id": "multi-step-signup",
"title": "Create your account",
"fields": [
{
"type": "text",
"name": "fullName",
"label": "Full name",
"required": true,
"rules": {
"minLength": 2
}
},
{
"type": "email",
"name": "email",
"label": "Email",
"required": true
},
{
"type": "password",
"name": "password",
"label": "Password",
"required": true,
"width": "half",
"complexity": {
"uppercase": true,
"lowercase": true,
"number": true,
"minLength": 8
}
},
{
"type": "password",
"name": "confirmPassword",
"label": "Confirm password",
"required": true,
"width": "half",
"rules": {
"matches": "password",
"matchesMessage": "Passwords don't match"
}
},
{
"type": "otp",
"name": "emailOtp",
"label": "Verification code",
"length": 6,
"dependsOn": "email"
},
{
"type": "submit",
"name": "submit",
"text": "Create account"
}
],
"steps": [
{
"title": "Account",
"fieldNames": [
"fullName",
"email",
"password",
"confirmPassword"
]
},
{
"title": "Verify",
"fieldNames": [
"emailOtp"
]
},
{
"title": "Review",
"review": true
}
]
});
export type Values = InferValues<typeof config>;
Live preview
Loading the live form…
What's already handled.
fields:24 built-in field types
Text, phone, OTP, signature, masked input, rating, and more — one component per type.visibleWhen:Conditional logic
Show, hide, enable, or disable fields with visibleWhen — no imperative wiring.steps:Multi-step wizards
Step-gated validation, conditional steps, and a read-only review step before submit.superRefine:Cross-field validation
Matching passwords, sibling date/time bounds — enforced by a form-level rule.otp:OTP verification flows
Send/verify codes per field, with submit gated on a verified-code registry.autosave:Autosave + draft restore
Drafts persist as the user types and restore on return, without extra config.Pick your tradeoff.
CapabilityHosted buildersHand-rolled RHFThis engine
Own the generated codeHosted builders: No — locked to a hosted runtimeHand-rolled RHF: Yes — but every line is yours to write
This engine: Yes — copy it into your app
Type-safe Zod validationHosted builders: Varies by platformHand-rolled RHF: Yes — hand-written schema per form
This engine: Yes — generated from the config
Conditional logic & wizardsHosted builders: Limited, platform-specificHand-rolled RHF: Yes — you build the state machine
This engine: Yes — visibleWhen + step config
Custom field typesHosted builders: Not supportedHand-rolled RHF: Yes — you write the component
This engine: Yes — registerField(), same registry as built-ins
Works without a vendor's serversHosted builders: No — renders and validates through their APIHand-rolled RHF: Yes
This engine: Yes — no runtime dependency on us
PricingHosted builders: Per-response or per-seatHand-rolled RHF: Free — but you built it
This engine: Free — MIT license, no seats