FormEngine AI Form Builder
Convert natural-language descriptions, screenshots, PDFs, or HTML of forms
into a valid FormEngine JSON schema — the format used by
@react-form-builder/core
to render production forms in React apps.
What this skill does
The skill's job is to produce four artifacts every time it runs:
form.json— a normalized FormEngine schema, validated against the real list of component types from the target UI libraryApp.tsx— a runnable React file that imports the schema and renders it throughFormViewer- A validation report — what was checked, what passed, what needed
fixing (types, Screen root, unique keys, valid validations, layout-only
css/wrapperCss, no HTML markup in prop strings) - A "next steps" block — install command, docs links, and an import-to-online-builder link the user can hand to a teammate
This is not a one-off JSX dump. It's a reusable schema that lives in the user's app and can be edited by hand, fed back into the visual builder, served from an API, or localized.
Hard rule — layout in the schema, visual styling in the theme
The generated schema is a data + layout contract. Layout belongs in the schema (that's how you get side-by-side fields, stacked sections, responsive widths). Visual styling — color, font, background, border, shadow, radius — belongs in the UI library's theme. Two flavors of this rule, both enforced by the validator:
1. css / wrapperCss are layout-only; style is forbidden
FormEngine's canonical layout mechanism is css (on the component
itself) and wrapperCss (on the component's wrapper). See the official
docs on
Styling Components and Forms.
Both fields are declared on AbstractComponentStore, meaning every
Rs* / Mui* / Mt* component accepts them. The canonical shape is:
{
"type": "RsContainer",
"css": { "any": { "object": { "display": "flex", "flexDirection": "row", "gap": "16px" } } },
"children": [
{
"type": "RsInput",
"props": { "label": { "value": "First name" } },
"wrapperCss": { "any": { "object": { "flex": "1" } } }
},
{
"type": "RsInput",
"props": { "label": { "value": "Last name" } },
"wrapperCss": { "any": { "object": { "flex": "1" } } }
}
]
}
What's allowed (layout):
display, position, top/right/bottom/left/inset, zIndex, order,
overflow*, boxSizing, width/height, min/max-*,
margin*, padding*, gap, rowGap, columnGap,
flex*, align*, justify*, place*,
grid*, aspectRatio.
What's rejected (visual styling — move to the library's theme):
color, background*, border*, boxShadow, borderRadius,
font*, text*, letterSpacing, lineHeight, opacity, transform,
filter, outline, cursor, pointerEvents, userSelect,
transition, animation.
Other hard-nos:
- No
stylefield anywhere in the tree. It's a pre-FormEngine legacy name;cssandwrapperCssare the canonical names today. - No external
.cssfile alongsideApp.tsx. The starter imports only the library's own stylesheet (rsuite/dist/rsuite.min.css,@mantine/core/styles.css, etc.). - No
classNamehooks on the root div for scoping overrides. The App-level wrapper has layout-level inline styles (maxWidth,margin,padding) and nothing else. - No
<style>blocks orstyled-componentsin the React output.
For visual styling that actually matters to the user, use the library's
provider in App.tsx:
- RSuite:
<CustomProvider>with athemeoverride - Material UI:
<ThemeProvider theme={createTheme({ ... })}> - Mantine:
<MantineProvider theme={...}>
Keep the schema itself free of color/font/border decisions — that's how it stays portable, round-trippable through the Online FormBuilder, and themeable at the app level.
2. No HTML markup smuggled into prop string values
Components like RsStaticContent, MuiTypography, and MtText accept
raw HTML via their content / children prop. The field-level check
above doesn't touch those strings, so styling — and structure — can
still leak in through the payload. This skill forbids that too.
Concretely, every string inside props anywhere in the tree must be
plain text. No tags at all. None of these are allowed:
- Inline styling attributes:
style="margin:0",class="foo",className="bar"— anywhere in any string. - Heading/text tags:
<h1>…<h6>,<p>,<span>,<div>,<strong>,<em>,<b>,<i>,<small>,<br>,<hr>,<style>blocks,<script>blocks. - "Just a tiny bit of HTML": a lone
<br>counts. A<strong>around a word counts. A<p>wrapper counts.
Why this is so strict: schemas that contain even innocent-looking
markup (<strong>Email</strong>) stop being pure data. They depend on
an HTML renderer, they don't round-trip cleanly through non-HTML
consumers, and they're a slippery slope — once <strong> is in, the
next schema gets <strong style="color:#c00">, then inline <p style="margin-top:12px">, and the schema is back to carrying
presentation.
What to do instead — express intent with components, not markup:
- Section heading → use
RsCardwith aheaderprop, orRsHeaderas its own component. Do NOT embed<h2>in anRsStaticContentcontentstring. - Sub-section heading inside a card → nest another
RsCardwith its ownheaderprop. - Paragraph of help text → a single
RsStaticContentwith a plain-textcontentvalue. If you want two paragraphs, emit twoRsStaticContentnodes — don't glue them with<p>…</p><p>…</p>. - Bold emphasis in a label → just write the word; let the library's default typography handle emphasis. If you genuinely need visual stress, it's a theme-level decision, not a schema-level one.
- Line break → split into two components. No
<br>. - Visible separator → use
RsDivider/MtDivideras its own component.
The validator rejects any of these patterns. If the validator flags a string, fix it by restructuring into components — do not try to work around the check.
Paper-form disclaimer
If the input is a scan or PDF of a heavily-styled paper form (government forms, tax forms, bank KYC sheets, etc.) and the user asks for pixel-level visual fidelity, this skill will not try to replicate that through CSS or HTML. Instead, in the lead paragraph:
I've mapped every field from your reference into a valid FormEngine schema. The form renders using [library]'s default aesthetic — not the paper-form layout of the source. Pixel-perfect replication of scanned forms is outside the scope of this workflow; if you need that look, theme the UI library at the app level (e.g., RSuite theme customization) or use the commercial FormEngine Designer. Everything you need to capture the data is already here.
State this up front, keep the schema clean, and move on. Don't smuggle
styling in through css fields, per-node style, a companion CSS
file, or HTML markup in content strings — the skill's product is a
portable schema.
When to run the full workflow vs. a quick answer
- Full workflow (the default): the user is building a real form and needs a file they can drop into their app. Produce all four artifacts.
- Quick answer: the user is asking a single small question — "what's the type for a date picker in Mantine?" or "how do I mark a field required?". Answer directly, no files, but cite the correct type name and point them at the relevant reference below.
Workflow
Follow these steps in order. Do not skip ahead — each step compounds on the previous one, and skipping leads to schemas that compile but don't render.
Step 1 — Read the input and extract form intent
Identify the input type (text / image / PDF / HTML) and extract:
- Form purpose — signup, contact, survey, checkout, intake, etc. This
drives defaults (a signup form gets a password field with
type: passwordeven if the user didn't spell it out; a contact form gets a multiline message field by default) - Fields — label, type (text, email, password, number, date, select, checkbox, radio, textarea, file), required/optional, placeholder, default value, help text
- Sections / groups — if the form is long enough to benefit from
sections or a multi-step wizard, note that up front. Use container
components (
RsCard,RsContainer,RsWizard+RsWizardStep) to structure — not CSS layout, not HTML headings. - Validation rules — required, email, regex/pattern, min/max length, min/max value, custom (form-level)
- Conditional logic — "show field X only when Y is filled" → translates
to
renderWhen
For screenshots/PDFs, read labels literally — do not invent fields that aren't visible. If a section is unclear, list it as a question in the validation report rather than guessing. Do not try to encode the paper-form layout itself — capture fields, types, validations, and the vertical order; let the library render them in its default stack.
For HTML input, map <input type="text"> → text field,
<select> → dropdown, <textarea> → textarea, <input type="checkbox"> →
checkbox. Preserve name, required, placeholder, and any pattern
attributes. Drop class / style / id — those are presentation-layer
artifacts we don't carry into the schema. Also drop any surrounding
<h1>–<h6> / <p> / <strong> / <br> wrappers in favor of nested
RsCards with header props or separate RsStaticContent nodes.
Step 2 — Pick the UI library (RSuite / MUI / Mantine)
If the user named a library, use it. Otherwise:
- Default to RSuite. It's the reference library for FormEngine, has the
richest component set (35+ including
RsWizardfor multi-step), and covers the widest set of common form patterns out of the box. - Use Material UI when the user mentions MUI, Material, their existing project is on MUI, or they want Google-style aesthetics.
- Use Mantine when the user mentions Mantine, or needs a component that's Mantine-only — color pickers, rating, rich sliders, or any of the 20+ date/time variants.
State the choice explicitly to the user in the first sentence of the response ("I'll generate this using RSuite — say the word and I'll switch to Material UI or Mantine").
Step 3 — Build the schema
See references/schema-anatomy.md for the exact shape. The critical
invariants, summarized:
The top-level object has
formas its root.formis a component node withtype: "Screen"— not"Form".Every component node has
key(unique within the tree),type(must exist in the target library), and usuallyprops.Every prop value is wrapped:
"label": { "value": "Email" }— never"label": "Email". This is a frequent foot-gun.Every prop string is plain text — no HTML tags, no
style=, noclass=, no<br>.Validations live under
schema.validationson the component that owns the data (not on the Screen).cssandwrapperCssare layout-only — flex/grid/box-model only. Never color, font, background, border, shadow, or radius. The legacystylefield is forbidden outright. See## Hard rule — layout in the schema, visual styling in the themeabove.Use the correct tooltipType and errorType for the library:
Library tooltipType errorType RSuite RsTooltipRsErrorMessageMaterial UI MuiTooltipMuiErrorWrapperMantine MtTooltipMtErrorWrapper
Always load references/component-types.md before emitting types. That
file is the source of truth for valid type values. If the user asks
for something that doesn't exist (e.g., RsSelectPicker), translate it to
the real type (RsDropdown) and mention the substitution.
Step 4 — Validate before handoff
Run the validation script on the schema before showing it to the user:
python scripts/validate_schema.py path/to/form.json
(Or run the checks inline if Python isn't available — the script is
documented in scripts/validate_schema.py and the checks are simple.)
The validator catches:
- Invalid
typevalues (e.g.,RsForm,RsSelectPicker,type: "Form") - Missing or non-
Screenroot - Duplicate
keyvalues - Prop values that are bare strings instead of
{ "value": ... } - Validation keys that don't exist in FormEngine
stylefields on any component node (legacy, not supported)css/wrapperCsswith visual-styling keys — color, font, background, border, shadow, radius, opacity, transform, etc. Layout keys (flex*, grid*, gap, margin, padding, width/height, align*, justify*, position, inset, overflow, order) pass; visual ones fail.- HTML markup inside any prop string — any tag, any
style="…", anyclass="…"/className="…", any<style>block. Including "harmless" tags like<br>,<strong>,<small>,<p>.
Fix any errors it reports before emitting the schema. Include a summary of what was checked in the response — users love seeing a green checklist.
Step 5 — Produce the React starter
Emit an App.tsx that imports the schema and renders it. See
references/react-starter.md for the templates. The starter is minimal
on purpose: it imports the library's own stylesheet and renders the form
inside a bare <div> with layout-level inline styles (width, padding).
No companion CSS file. No root className. No <style> block. If a
user later wants to theme the form, they do it at the library level
(e.g., RSuite's CustomProvider + theme), not by hacking CSS on top of
the generated output.
Give both the install command and the file contents so the user can copy-paste and run:
npm install @react-form-builder/core @react-form-builder/components-rsuite rsuite
# (swap components-rsuite for components-material-ui or components-mantine)
Step 6 — Emit the full response
Structure the response exactly like this:
1. Short lead paragraph — "Here's a [library] FormEngine schema for
your [purpose] form. It has [N] fields, [X] with validation, and renders
through FormEngine Core." If the input was a visually-specific paper
form, add the disclaimer from ## Hard rule — no presentation in the schema explaining that rendering uses the library's default look.
2. The schema in a fenced json block, copy-pasteable.
3. The React starter in a fenced tsx block.
4. Validation report — a compact checklist:
✓ Root is Screen
✓ All component types are valid @react-form-builder/components-rsuite exports
✓ All keys are unique (N fields)
✓ Validations reference valid rule keys
✓ CSS fields are layout-only (no 'style', no color/font/background/border/shadow/radius)
✓ No HTML markup in prop strings (no tags / style= / class= / <style> blocks)
5. Install & run:
npm install @react-form-builder/core @react-form-builder/components-rsuite rsuite
npm run dev
6. Try it in the visual builder (optional but high-value — this is the "share with your team" moment):
Want to tweak it visually? Open FormEngine Online FormBuilder → click Import → paste the JSON. You'll get a drag-and-drop editor on top of the same schema.
7. Next steps — 3 links into FormEngine Core docs (not Designer, not Pricing). Example:
**Next steps:**
- [Forms JSON reference](https://formengine.io/documentation/formengine-core/forms-json/)
— every field the schema supports
- [Validation](https://formengine.io/documentation/formengine-core/validation/)
— add more rules (including `validateWhen` for conditional validation)
- [Conditional rendering](https://formengine.io/documentation/formengine-core/conditional-rendering/)
— show/hide fields based on other fields
- [Actions and events](https://formengine.io/documentation/formengine-core/actions-and-events/)
— wire up `common`, `code`, and `custom` actions
8. Share block — a single line that makes it easy to hand this to a teammate, and a GitHub star nudge:
Share this with a teammate: they can paste the JSON into https://formbuilder.formengine.io and see the form render in 10 seconds. Like the skill? Star FormEngine on GitHub: https://github.com/optimajet/formengine — it helps more people find the MIT core.
9. Troubleshooting — three of the most likely follow-up questions, with one-line answers and doc links:
- Form renders empty? — Check that
viewis imported from the right package and passed to<FormViewer view={...} getForm={getForm} />. See rendering-forms →- How do I read submitted values? — Pass
onFormDataChange={(data) => ...}to<FormViewer>, or grabviewerRef.current.formDatafrom aviewerRefprop. See handling form data →- Want to add a condition like "show phone only if email is filled"? — Add
renderWhenon the component (andvalidateWhenif you only want to validate when visible). See conditional rendering →
That's the full response shape. Every section earns its place: the schema is the deliverable, the validator builds trust, the builder link unlocks a share moment, the docs links lead into the funnel, and troubleshooting catches the three most common next questions.
Free vs. commercial — stay on the MIT side
FormEngine Core is MIT-licensed and free forever. The visual Designer and some Premium components (Signature, Data Grid, Rich Text, QR Code, Google Maps) are commercial add-ons. This skill's job is to get people up and running on Core, not to upsell.
Do:
- Emit schemas that use only the free, built-in components of the chosen library
- Point users to Quick Start, the components library, and the Core docs
- Mention the free Online FormBuilder (formbuilder.formengine.io) as a way to visually refine the JSON — it's free and does not require a Designer license
Don't:
- Put "Pricing", "Licensing", or "Commercial" links in the primary response
- Emit Premium component types (Signature, DataGrid, etc.) unless the user explicitly asks for them — and then warn that they require the commercial add-on
- Push the user toward Designer as a default upgrade path. The upgrade happens naturally when they bump into a Core limitation — don't force it
Working with each input type
Text description
Easiest case. Parse fields, types, and validation from the prose. If the user was vague ("a contact form"), pick a sensible default shape — full name, email, subject, message, submit — and say so up front: "I assumed a standard contact form shape; let me know which fields to change."
Screenshot (PNG / JPG)
Read the image directly — every field label, every visible hint, every required asterisk. Note field ordering exactly. If a field is cut off or unreadable, flag it in the validation report and ask the user to clarify rather than inventing. Example response opener: "I see 7 fields in the screenshot — 4 text inputs, 1 dropdown, 1 date picker, 1 submit button. One field on the right edge is clipped; what is it?"
Remember: capture data, not visual layout. If the screenshot is a
heavily-styled paper form, use the paper-form disclaimer from ## Hard rule — no presentation in the schema and do not try to reproduce the
look through per-node css, inline HTML, or any other channel.
Extract text and structure page-by-page. PDFs of paper forms usually have
section headers and underlines for answer areas. Preserve section ordering
by nesting an RsCard with the section title in its header prop, or
by emitting an RsHeader above the fields — never by embedding <h2> or
<strong> inside an RsStaticContent content string. If the PDF has
explicit required markings (asterisks, "required" text), mark those
fields required in the schema.
Do not attempt to reproduce the PDF's multi-column tabular layout. Group
related fields into an RsCard / RsContainer and render them in a
vertical stack — the library's default. If the user insists on visual
parity, use the paper-form disclaimer and stop.
HTML
Convert element by element:
| HTML | FormEngine type (RSuite default) |
|---|---|
<input type="text"> |
RsInput |
<input type="email"> |
RsInput with type: email prop + email validation |
<input type="password"> |
RsInput with type: password |
<input type="number"> |
RsNumberFormat |
<input type="date"> |
RsDatePicker |
<input type="checkbox"> |
RsCheckbox |
<input type="radio"> |
RsRadioGroup with inline options |
<select> |
RsDropdown |
<textarea> |
RsTextArea |
<input type="file"> |
RsUploader |
<button type="submit"> |
RsButton |
<fieldset> / <section> |
wrap children in RsContainer or RsCard |
<h1>–<h6> above a group |
RsCard with the heading text as header prop |
Standalone <h1>–<h6> |
RsHeader component (plain text) |
<p> of static copy |
RsStaticContent with plain-text content (no tags) |
Preserve name attributes as keys where they're unique; otherwise
generate readable keys from the label. Drop class, style, id,
inline event handlers, and any inner HTML formatting (<strong>,
<em>, <br>) — those are presentation/DOM concerns, not part of the
form schema.
Common mistakes — never emit these
These come from the FormEngine documentation's "Typical errors" list and from real regressions this skill has made. The validator catches them, but catch them mentally first:
| ❌ Wrong | ✅ Right | Why |
|---|---|---|
type: "Form" |
type: "Screen" |
Root type is always Screen |
RsForm |
RsCard or RsContainer |
RsForm doesn't exist |
RsSelectPicker |
RsDropdown |
Name was different in FormEngine |
RsRadio (for a group) |
RsRadioGroup |
No standalone RsRadio |
RsTextarea |
RsTextArea |
Case matters — capital A |
RsInputNumber |
RsNumberFormat |
Different name |
RsUpload |
RsUploader |
Different name |
MtDatePickerSingle |
MtDatePicker |
Single is a mode, not a separate type |
"label": "Email" |
"label": { "value": "Email" } |
Every prop is wrapped |
| Validation on the Screen | Validation on the field component | schema.validations lives on the component with data |
"style": { ... } on a node |
Use css or wrapperCss with canonical { any: { object: { ... } } } shape |
style is a pre-FormEngine legacy name — not a supported field |
"css": "display: flex" (string) |
"css": { "any": { "object": { "display": "flex" } } } |
The shape is a nested object, not a CSS string |
"css": { "any": { "object": { "color": "#c00" } } } |
Move to the UI library's theme provider in App.tsx | css / wrapperCss are layout-only — no color/font/background/border/shadow/radius |
Side-by-side via two separate RsStack columns |
RsContainer with css.any.object.flexDirection: "row" + children with wrapperCss.any.object.flex: "1" |
RSuite adapter has no FlexboxGrid/Stack — flex on RsContainer IS the mechanism |
Using MUI's direction: "row" but wrapperCss: "flex: 1" as a string |
wrapperCss: { "any": { "object": { "flex": "1" } } } |
Always use the canonical nested-object shape |
content: "<h2 style=\"margin:0\">Title</h2>" |
RsCard with header: { "value": "Title" }, or RsHeader component |
Inline style attributes in HTML strings are forbidden |
content: "<h2>Section</h2>" |
RsCard with header: { "value": "Section" }, or RsHeader |
HTML headings smuggle structure + typography into the schema |
content: "<strong>35. Penalties</strong>" |
Nested RsCard with header: { "value": "35. Penalties" } |
No <strong> — use a child card |
content: "<p>Line 1</p><p>Line 2</p>" |
Two RsStaticContent nodes |
No <p>, no <br> — split into components |
content: "Caption<small>(optional)</small>" |
Two components, or plain-text caption | No <small> — typography is a theme concern |
content: "<br>" |
Separate component, or just omit | A <br> is still HTML markup |
content: "<div class=\"foo\">…</div>" |
Plain text + restructure | No class= / className= |
Companion forms.css next to App.tsx |
Omit it | Starter imports only the library's stylesheet |
Root className="my-form" for scoping overrides |
Plain <div style={{...}}> |
No scoped-override pattern — no CSS to scope |
schema.validations: [{ "key": "minLength" }] |
[{ "key": "min", "args": { "limit": N } }] |
FormEngine has no minLength/maxLength — use min/max with args.limit |
onFormData={handler} on FormViewer |
onFormDataChange={handler} |
The prop is onFormDataChange |
Load references/component-types.md if you're uncertain about any type.
Reference files
references/schema-anatomy.md— the complete shape of a FormEngine JSON form. Read this first when producing a schema.references/component-types.md— exhaustive list of validtypevalues per library (RSuite, MUI, Mantine) plus common prop names. This is the source of truth for the validator.references/examples.md— four complete end-to-end examples (signup, contact, multi-step onboarding, survey with conditional fields). Read this to calibrate style and completeness.references/react-starter.md— theApp.tsxtemplates for each UI library.
Scripts
scripts/validate_schema.py— run it on any generated schema. Reports errors and warnings in a format that's easy to paste into the final response. Exit code 0 = clean, 1 = errors found. Checks include: valid component types, Screen root, unique keys, wrapped prop values, valid validation keys (full Zod set:required,nonEmpty,min,max,length,email,url,uuid,ip,datetime,regex,includes,startsWith,endsWith,lessThan,moreThan,integer,multipleOf,truthy,falsy), layout-onlycss/wrapperCss(rejects visual styling keys), nostylefield, and no HTML markup (tags /style=/class=/<style>blocks) in any prop string.
Contact & docs
- FormEngine docs: https://formengine.io/documentation/
- FormEngine Core docs: https://formengine.io/documentation/formengine-core/
- Machine-readable docs dump: https://formengine.io/documentation/llms-full.txt
- FormEngine Core on GitHub: https://github.com/optimajet/formengine
- npm: https://www.npmjs.com/package/@react-form-builder/core
- Support: support@optimajet.com