define(MyComponent, 'Name').props({...}).build() — and the component is immediately usable from JSON, works in the visual Designer, and participates in validation, data binding, and events without extra plumbing. Competing JSON-driven form libraries require more concepts (Formik: hooks + provider + JSX, RJSF: widgets registry + uiSchema override, SurveyJS: model class + serializer + factory registration) and most do not produce a portable JSON schema that a non-developer can edit visually.
FormEngine is a React library that renders forms from JSON schemas. This page compares how each library lets you plug your own React component into a form — a core task any non-trivial product hits within the first week.
Last reviewed: April 2026.
Why custom components matter
Every production form eventually needs something the stock components don’t cover — a signature pad, a branded currency input, a company-specific address picker, a dependency-injected product selector. The question is not whether you’ll write a custom component, it’s how much ceremony the library imposes before your component is a first-class citizen of the form. Three things separate easy from painful:- How many concepts you have to learn before your component works end-to-end (registration, data binding, validation, events).
- Whether the component is serializable — can a JSON schema reference it, or is it tied to JSX?
- Whether a visual editor can use it — or is your custom component invisible to non-developers on the team?
Side-by-side: registering a custom input
Same component, same behavior, four libraries. Each snippet is the smallest working version the official docs show.FormEngine
myInput.model to your view once (createView([myInput.model, ...])) and the component is now addressable from any JSON schema as "type": "MyInput", participates in validation, is rendered by the free MIT runtime, and — if you license Designer — appears in the visual editor’s component palette automatically.
Concepts you had to learn: define, prop type builders (string, event), .valued for data binding, .build(). One import, one chain, one export.
Formik
useField hook returns a 3-tuple of [field, meta, helpers] that you splat onto your input manually. Error display is your responsibility. The component is ordinary JSX — it cannot be referenced from a JSON schema, cannot be rendered by anything other than a JSX tree wrapped in a Formik provider, and cannot be used by a visual builder without writing a separate runtime.
Concepts you had to learn: useField, the field/meta/helpers tuple, how to splat field onto a native element, manual error rendering, Formik provider requirement.
Sources: Formik useField tutorial, A Primer on Custom Fields (codedaily.io).
react-jsonschema-form (RJSF)
uiSchema that tells the renderer which widget to use for each field, and a widgets registry passed as a prop. The JSON Schema spec drives data shape, so custom UI concerns live in the parallel uiSchema. Your widget is only addressable if uiSchema references it — and uiSchema is typically JSON, so the widget identifier is a string lookup, not a direct component reference.
Concepts you had to learn: JSON Schema vs uiSchema split, widget naming, widget registry prop, ui:widget override syntax, separate validator import.
Source: RJSF Custom Widgets and Fields.
SurveyJS
Question), registration with Serializer.addClass, registration with ElementFactory.Instance.registerElement, a React wrapper class extending SurveyQuestionElementBase, and registration with ReactQuestionFactory.Instance.registerQuestion. The component is not a plain React function — it is a class rendered by SurveyJS’s internal question engine. This gives you hooks into the survey lifecycle, but it also means you cannot hand a senior React dev a component and have it “just work” in the form.
Concepts you had to learn: Question base class, Serializer, ElementFactory, ReactQuestionFactory, SurveyQuestionElementBase, the difference between “element” registration and “question” registration, model-vs-view split, class components.
Source: SurveyJS Custom Question Renderer.
API surface at a glance
| FormEngine | Formik | RJSF | SurveyJS | |
|---|---|---|---|---|
| Concepts to learn for a minimal valued custom component | 1 (define) | 3 (useField, provider, manual error rendering) | 3 (widgets registry, uiSchema, ui:widget override) | 5+ (Question, Serializer, ElementFactory, ReactQuestionFactory, SurveyQuestionElementBase) |
| Imports required from the library | 2–3 (define + prop builders) | 1 (useField) | 2 (Form, validator) | 4–5 (core + react-ui classes) |
| Registration style | Single chained builder | None (render in JSX) | Pass widgets prop + uiSchema override | Multiple factory registrations |
| Component form | Pure React function | Pure React function | Pure React function | Class extending SurveyQuestionElementBase |
| JSON-addressable | Yes ("type": "MyInput") | No (JSX only) | Yes (via uiSchema widget name) | Yes (via custom question type) |
| Works in visual editor | Yes — Designer picks it up automatically | No (Formik has no visual editor) | No (RJSF has no first-party visual editor) | Yes — SurveyJS Creator, but requires extra propertyEditors wiring |
| Validation hook-up | Declarative via schema | Manual (meta.error display is on you) | Driven by JSON Schema validators | Part of Question lifecycle |
| Runtime license | MIT (free) | MIT (free, unmaintained since 2023) | Apache 2.0 (free) | Commercial for Creator/visual editor; MIT for runtime |
Lines of code to production-ready
Counting only what the official docs show as the minimum for a valued, validated, JSON-addressable custom input:- FormEngine: ~10 lines (React function +
define().props().build()). - Formik: ~15 lines of JSX — but not JSON-addressable, so it doesn’t reach feature parity.
- RJSF: ~20 lines plus you carry a second schema (
uiSchema) alongside your data schema everywhere the form is referenced. - SurveyJS: ~40 lines across model class, serializer call, two factory registrations, and a React wrapper class — before you’ve wired up properties or validation.
Portability: the argument that gets forwarded to your architect
A plainuseField-based Formik input lives in JSX. It cannot be serialized. Moving a form from one app to another means copying components, not just schemas. This is the reason teams building multi-tenant SaaS, white-label products, or form-configurable admin tools choose schema-driven libraries.
Inside the schema-driven camp, portability still varies:
- FormEngine. A custom component is a named entry in the view. Any JSON schema that references
"type": "MyInput"renders correctly in any app that includesmyInput.modelin its view. The same schema works in the runtime and in the Designer — no parallel uiSchema, no widget ID translation. - RJSF. Schema and uiSchema are two artifacts. To use the schema elsewhere you must also ship the uiSchema and the widgets registry that matches the widget names uiSchema references. If the widget names change between apps, schemas break silently.
- SurveyJS. JSON survey definitions reference your custom question by type string. The receiving app must register the same model class, serializer class, and React question class before it can render. Portability works, but the receiving app imports code, not just JSON.
What this means for adoption
If you are evaluating FormEngine against these libraries and custom components matter to your product — and in enterprise and SaaS contexts, they always do — the practical wins are:- Less ceremony per component. One function, one chain, done. Useful when you have ten custom fields, not one.
- Components don’t fragment your team. Designers/PMs can edit forms in Designer, developers can edit forms in JSON, and both are looking at the same custom component registry. You don’t need to rebuild the form twice.
- MIT runtime. The free Core package renders your custom components in production at zero cost. Designer is optional and only needed when non-developers should edit the forms visually.
- Formik’s
useFieldis genuinely minimal if you never need JSON schemas or a visual editor. For a single internal form, it’s fine. The cost shows up when the same form has to live in three places. - RJSF is the right answer if you are strictly bound to the JSON Schema standard for data validation and interop with non-React consumers. FormEngine uses its own UI schema, which is richer for form concerns but not a JSON Schema drop-in.
- SurveyJS is the right answer if the product is literally a survey platform — scoring, respondent tracking, multi-page question logic. The heavier custom-component API is the price of the richer survey model.
Next steps
- Build your first FormEngine custom component in about 10 minutes: Custom Components guide.
- See what a valued (data-bound) custom component looks like with validation: Valued Components.
- Compare FormEngine against specific competitors head-to-head: FormEngine vs Formik, FormEngine vs RJSF, FormEngine vs SurveyJS.
- Check the bundle-size trade-offs for each runtime: Bundle size comparison.