Skip to main content
Generating UI from natural language is powerful but fragile. If the model outputs a component name your renderer doesn’t support, a field name with spaces, or 50 form fields for a simple request, your frontend either crashes or renders garbage. A strict schema turns the model’s output into a guaranteed-renderable form spec: known component types, valid field names, bounded counts. The key insight is that your schema should mirror what your renderer actually accepts. If your React form builder supports six component types, the schema’s component enum should list exactly those six. If your layout engine supports single and two-column modes, the schema should enumerate those. The model generates within these bounds, and the output is always renderable.

Goal

Convert product requests into a form specification that a React frontend can render directly without validation or transformation.
{
  "type": "object",
  "properties": {
    "title": { "type": "string", "minLength": 3, "maxLength": 80 },
    "layout": { "type": "string", "enum": ["single_column", "two_column"] },
    "fields": {
      "type": "array",
      "minItems": 1,
      "maxItems": 20,
      "items": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" },
          "label": { "type": "string", "minLength": 1, "maxLength": 50 },
          "component": {
            "type": "string",
            "enum": ["text", "email", "number", "select", "textarea", "checkbox"]
          },
          "required": { "type": "boolean" },
          "placeholder": { "type": "string", "maxLength": 80 },
          "options": {
            "type": "array",
            "items": { "type": "string", "minLength": 1, "maxLength": 40 },
            "maxItems": 20
          }
        },
        "required": ["name", "label", "component", "required"],
        "additionalProperties": false,
        "allOf": [
          {
            "if": { "properties": { "component": { "const": "select" } } },
            "then": { "required": ["options"] }
          }
        ]
      }
    }
  },
  "required": ["title", "layout", "fields"],
  "additionalProperties": false
}

Prompt pattern

You generate UI form specs for a React renderer.
Return JSON only and follow the schema exactly.
Keep labels concise and names snake_case.
Use select only when options are explicit in the request.

Example input

Build a lead capture form for B2B demo requests.
Need company email, team size with options 1-10, 11-50, 51-200, and 201+, and whether they want migration help.

Example output

{
  "title": "B2B Demo Request",
  "layout": "two_column",
  "fields": [
    {
      "name": "company_email",
      "label": "Company Email",
      "component": "email",
      "required": true,
      "placeholder": "name@company.com"
    },
    {
      "name": "team_size",
      "label": "Team Size",
      "component": "select",
      "required": true,
      "options": ["1-10", "11-50", "51-200", "201+"]
    },
    {
      "name": "needs_migration_help",
      "label": "Need Migration Help",
      "component": "checkbox",
      "required": false
    }
  ]
}

Implementation tips

  • Component enum mirrors your renderer. If your form builder gains a new component type (e.g., date_picker), add it to the schema enum. If the model suggests a type not in the enum, the schema rejects it at generation time.
  • Conditional requirements prevent incomplete specs. The allOf block requires options when component is "select". Without this, the model might produce a select field with no options: valid JSON, but unrenderable.
  • Bounds protect layout. maxItems: 20 on fields prevents the model from generating overwhelming forms. maxLength on labels and placeholders keeps text from breaking your CSS grid.
  • Field name pattern keeps keys predictable. The "^[a-z][a-z0-9_]*$" pattern on name enforces lowercase snake_case field keys, which keeps form state and backend mappings consistent without extra normalization.