← Back to Blog

Conditional Form Logic: Show and Hide Fields Dynamically

Not every user needs to see every field. A billing form should only show credit card fields when the user selects "Credit Card" as their payment method. A registration form should only ask for a company name when the user identifies as a business customer. Showing irrelevant fields creates clutter, increases cognitive load, and lowers completion rates.

FormForge supports conditional field visibility through its conditions system. You define rules in your JSON schema that tell FormForge which fields depend on other fields' values. The generated HTML includes the JavaScript needed to show, hide, and validate fields dynamically as the user fills out the form.

The Conditions System

FormForge's conditional logic is defined at the field level using a conditions property. Each condition specifies a source field, an operator, and a value. When the condition evaluates to true, the field is visible. When it evaluates to false, the field is hidden and excluded from validation and submission.

Here is the basic structure:

json
{
  "name": "companyName",
  "type": "text",
  "label": "Company Name",
  "required": true,
  "conditions": [
    {
      "field": "accountType",
      "operator": "equals",
      "value": "business"
    }
  ]
}

The supported operators cover the most common comparison patterns:

When multiple conditions are specified on a single field, all of them must be true for the field to be visible (AND logic). This lets you create precise visibility rules that depend on multiple inputs.

Real-World Example: Payment Method Form

A checkout form needs different fields depending on how the customer wants to pay. Credit card payments need card details. Bank transfers need account information. Digital wallets need none of these. Here is how to model this with FormForge:

json
{
  "title": "Payment Information",
  "theme": "modern",
  "submitUrl": "https://your-api.com/checkout",
  "fields": [
    {
      "name": "paymentMethod",
      "type": "radio",
      "label": "Payment Method",
      "required": true,
      "options": ["Credit Card", "Bank Transfer", "PayPal"]
    },
    {
      "name": "cardNumber",
      "type": "text",
      "label": "Card Number",
      "required": true,
      "pattern": "^[0-9]{13,19}$",
      "placeholder": "4242 4242 4242 4242",
      "conditions": [
        { "field": "paymentMethod", "operator": "equals", "value": "Credit Card" }
      ]
    },
    {
      "name": "cardExpiry",
      "type": "text",
      "label": "Expiry Date",
      "required": true,
      "placeholder": "MM/YY",
      "pattern": "^(0[1-9]|1[0-2])\\/[0-9]{2}$",
      "conditions": [
        { "field": "paymentMethod", "operator": "equals", "value": "Credit Card" }
      ]
    },
    {
      "name": "cardCvc",
      "type": "text",
      "label": "CVC",
      "required": true,
      "pattern": "^[0-9]{3,4}$",
      "placeholder": "123",
      "conditions": [
        { "field": "paymentMethod", "operator": "equals", "value": "Credit Card" }
      ]
    },
    {
      "name": "bankName",
      "type": "text",
      "label": "Bank Name",
      "required": true,
      "conditions": [
        { "field": "paymentMethod", "operator": "equals", "value": "Bank Transfer" }
      ]
    },
    {
      "name": "accountNumber",
      "type": "text",
      "label": "Account Number",
      "required": true,
      "conditions": [
        { "field": "paymentMethod", "operator": "equals", "value": "Bank Transfer" }
      ]
    },
    {
      "name": "routingNumber",
      "type": "text",
      "label": "Routing Number",
      "required": true,
      "conditions": [
        { "field": "paymentMethod", "operator": "equals", "value": "Bank Transfer" }
      ]
    },
    {
      "name": "paypalEmail",
      "type": "email",
      "label": "PayPal Email Address",
      "required": true,
      "conditions": [
        { "field": "paymentMethod", "operator": "equals", "value": "PayPal" }
      ]
    }
  ]
}

When the user selects "Credit Card," only the card number, expiry, and CVC fields appear. When they select "Bank Transfer," those fields disappear and the bank details fields appear instead. Selecting "PayPal" hides everything except the PayPal email field. The generated JavaScript handles all of this automatically.

Key behavior: Hidden fields are excluded from both validation and submission data. When a field is hidden because its condition is not met, its required attribute is effectively disabled. The user cannot be blocked by a required field they cannot see.

If/Then/Else Patterns

Beyond simple field-level visibility, FormForge supports schema-level if/then/else blocks that let you define complex conditional logic affecting multiple fields at once. This pattern is useful when a single user choice should change an entire section of the form:

json
{
  "title": "Event Registration",
  "theme": "modern",
  "fields": [
    {
      "name": "name",
      "type": "text",
      "label": "Full Name",
      "required": true
    },
    {
      "name": "email",
      "type": "email",
      "label": "Email",
      "required": true
    },
    {
      "name": "attendanceType",
      "type": "radio",
      "label": "How will you attend?",
      "required": true,
      "options": ["In Person", "Virtual"]
    }
  ],
  "conditionalSections": [
    {
      "if": { "field": "attendanceType", "equals": "In Person" },
      "then": {
        "fields": [
          {
            "name": "dietaryRestrictions",
            "type": "select",
            "label": "Dietary Restrictions",
            "options": ["None", "Vegetarian", "Vegan", "Gluten-Free", "Other"]
          },
          {
            "name": "tshirtSize",
            "type": "select",
            "label": "T-Shirt Size",
            "options": ["XS", "S", "M", "L", "XL", "XXL"]
          },
          {
            "name": "parkingPass",
            "type": "checkbox",
            "label": "I need a parking pass"
          }
        ]
      },
      "else": {
        "fields": [
          {
            "name": "timezone",
            "type": "select",
            "label": "Your Timezone",
            "required": true,
            "options": ["US/Eastern", "US/Central", "US/Mountain", "US/Pacific", "Europe/London", "Europe/Berlin", "Asia/Tokyo"]
          },
          {
            "name": "streamingPlatform",
            "type": "radio",
            "label": "Preferred Streaming Platform",
            "options": ["Zoom", "Google Meet", "No Preference"]
          }
        ]
      }
    }
  ]
}

In-person attendees see fields for dietary restrictions, t-shirt size, and parking. Virtual attendees see timezone and platform preference fields instead. The form adapts its entire lower section based on a single radio button selection.

Chaining Conditions: Nested Dependencies

Conditions can depend on fields that are themselves conditional. This creates a chain of dependencies where fields appear progressively as the user makes selections. FormForge evaluates the entire dependency graph on every change, so nested conditions work automatically.

Consider a support ticket form where the category selection determines the subcategory options, and the subcategory determines what additional details to collect:

json
{
  "title": "Submit a Support Ticket",
  "theme": "modern",
  "fields": [
    {
      "name": "category",
      "type": "select",
      "label": "Issue Category",
      "required": true,
      "options": ["Billing", "Technical", "Account"]
    },
    {
      "name": "billingIssue",
      "type": "select",
      "label": "Billing Issue Type",
      "required": true,
      "options": ["Incorrect Charge", "Refund Request", "Invoice Needed"],
      "conditions": [
        { "field": "category", "operator": "equals", "value": "Billing" }
      ]
    },
    {
      "name": "chargeDate",
      "type": "date",
      "label": "Date of Incorrect Charge",
      "required": true,
      "conditions": [
        { "field": "category", "operator": "equals", "value": "Billing" },
        { "field": "billingIssue", "operator": "equals", "value": "Incorrect Charge" }
      ]
    },
    {
      "name": "chargeAmount",
      "type": "number",
      "label": "Charge Amount ($)",
      "required": true,
      "min": 0,
      "conditions": [
        { "field": "category", "operator": "equals", "value": "Billing" },
        { "field": "billingIssue", "operator": "equals", "value": "Incorrect Charge" }
      ]
    },
    {
      "name": "technicalIssue",
      "type": "select",
      "label": "Technical Issue Type",
      "required": true,
      "options": ["API Error", "Slow Performance", "Integration Bug"],
      "conditions": [
        { "field": "category", "operator": "equals", "value": "Technical" }
      ]
    },
    {
      "name": "errorCode",
      "type": "text",
      "label": "Error Code or Message",
      "placeholder": "e.g., 429 Too Many Requests",
      "conditions": [
        { "field": "category", "operator": "equals", "value": "Technical" },
        { "field": "technicalIssue", "operator": "equals", "value": "API Error" }
      ]
    },
    {
      "name": "description",
      "type": "textarea",
      "label": "Describe Your Issue",
      "required": true,
      "minLength": 20
    }
  ]
}

The user selects "Billing" as the category, which reveals the billing issue type dropdown. Selecting "Incorrect Charge" then reveals the charge date and amount fields. At each step, the form grows to collect exactly the information needed for that specific issue type.

Building Conditional Forms with JavaScript

Here is a complete example of sending a conditional form schema to FormForge and embedding the result. The generated HTML already contains the conditional logic, so no additional client-side wiring is needed:

javascript
const schema = {
  title: "Shipping Information",
  theme: "modern",
  submitUrl: "https://your-api.com/orders/shipping",
  fields: [
    {
      name: "deliveryType",
      type: "radio",
      label: "Delivery Type",
      required: true,
      options: ["Standard", "Express", "Pickup"],
    },
    {
      name: "streetAddress",
      type: "text",
      label: "Street Address",
      required: true,
      conditions: [
        { field: "deliveryType", operator: "not_equals", value: "Pickup" },
      ],
    },
    {
      name: "city",
      type: "text",
      label: "City",
      required: true,
      conditions: [
        { field: "deliveryType", operator: "not_equals", value: "Pickup" },
      ],
    },
    {
      name: "zipCode",
      type: "text",
      label: "ZIP Code",
      required: true,
      pattern: "^[0-9]{5}(-[0-9]{4})?$",
      conditions: [
        { field: "deliveryType", operator: "not_equals", value: "Pickup" },
      ],
    },
    {
      name: "expressUrgency",
      type: "select",
      label: "Express Delivery Speed",
      required: true,
      options: ["Next Day", "2-Day"],
      conditions: [
        { field: "deliveryType", operator: "equals", value: "Express" },
      ],
    },
    {
      name: "pickupLocation",
      type: "select",
      label: "Pickup Location",
      required: true,
      options: ["Downtown Store", "Airport Kiosk", "Warehouse"],
      conditions: [
        { field: "deliveryType", operator: "equals", value: "Pickup" },
      ],
    },
    {
      name: "instructions",
      type: "textarea",
      label: "Delivery Instructions",
      placeholder: "Gate code, buzzer number, etc.",
      conditions: [
        { field: "deliveryType", operator: "not_equals", value: "Pickup" },
      ],
    },
  ],
};

async function renderConditionalForm() {
  const res = await fetch(
    "https://formforge-api.vercel.app/api/json-to-form",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": "Bearer ff_live_your_key",
      },
      body: JSON.stringify(schema),
    }
  );

  const { html } = await res.json();

  // Embed the form directly into your page
  document.getElementById("form-container").innerHTML =
    `<iframe srcdoc="${html.replace(/"/g, '&quot;')}"
       style="width:100%;min-height:500px;border:none;"
       sandbox="allow-scripts allow-forms">
     </iframe>`;
}

Python Example: Dynamic Registration Form

Here is a Python implementation of a registration form where the fields adapt based on whether the user is signing up as an individual or a business. The conditional schema is defined on the server and rendered via FormForge:

python
import os
import requests

FORMFORGE_URL = "https://formforge-api.vercel.app/api/json-to-form"
FORMFORGE_KEY = os.environ["FORMFORGE_KEY"]

registration_schema = {
    "title": "Create Account",
    "theme": "modern",
    "submitUrl": "https://your-api.com/register",
    "fields": [
        {
            "name": "accountType",
            "type": "radio",
            "label": "Account Type",
            "required": True,
            "options": ["Individual", "Business"],
        },
        {
            "name": "fullName",
            "type": "text",
            "label": "Full Name",
            "required": True,
        },
        {
            "name": "email",
            "type": "email",
            "label": "Email Address",
            "required": True,
        },
        # Business-only fields
        {
            "name": "companyName",
            "type": "text",
            "label": "Company Name",
            "required": True,
            "conditions": [
                {"field": "accountType", "operator": "equals", "value": "Business"}
            ],
        },
        {
            "name": "taxId",
            "type": "text",
            "label": "Tax ID / EIN",
            "conditions": [
                {"field": "accountType", "operator": "equals", "value": "Business"}
            ],
        },
        {
            "name": "teamSize",
            "type": "select",
            "label": "Team Size",
            "options": ["1-10", "11-50", "51-200", "200+"],
            "conditions": [
                {"field": "accountType", "operator": "equals", "value": "Business"}
            ],
        },
        # Individual-only field
        {
            "name": "useCase",
            "type": "select",
            "label": "Primary Use Case",
            "options": ["Personal Project", "Freelance", "Learning", "Other"],
            "conditions": [
                {"field": "accountType", "operator": "equals", "value": "Individual"}
            ],
        },
    ],
}


def render_registration_form():
    """Render the conditional registration form via FormForge."""
    resp = requests.post(
        FORMFORGE_URL,
        json=registration_schema,
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {FORMFORGE_KEY}",
        },
    )
    resp.raise_for_status()
    return resp.json()["html"]

Combining Conditions with Multi-Step Wizards

Conditional logic and multi-step wizards work together naturally. You can define conditions within individual wizard steps so that fields within a step appear or hide based on earlier answers in that same step. You can also use data collected in earlier steps to conditionally include entire later steps:

javascript
// Conditionally include wizard steps based on earlier data
function buildWizardSteps(collectedData) {
  const steps = [accountStep, profileStep];

  // Only show billing step if user chose a paid plan
  if (collectedData.plan !== "Free") {
    steps.push(billingStep);
  }

  // Only show team step for business accounts
  if (collectedData.accountType === "Business") {
    steps.push(teamSetupStep);
  }

  steps.push(confirmationStep);
  return steps;
}

// Within each step, conditions handle field-level visibility
// The billingStep schema itself uses conditions to show
// different fields for credit card vs bank transfer
const billingStep = {
  title: "Billing Information",
  theme: "modern",
  fields: [
    {
      name: "paymentMethod",
      type: "radio",
      label: "Payment Method",
      required: true,
      options: ["Credit Card", "Bank Transfer"],
    },
    {
      name: "cardNumber",
      type: "text",
      label: "Card Number",
      required: true,
      conditions: [
        { field: "paymentMethod", operator: "equals", value: "Credit Card" },
      ],
    },
    // ... more conditional fields
  ],
};

Validation and Hidden Fields

One of the most common pitfalls with conditional forms is validation of hidden fields. If a required field is hidden because its condition is not met, should the form still require it? With FormForge, the answer is clear: hidden fields are completely removed from the validation cycle.

This means you can safely mark conditional fields as "required": true without worrying about them blocking submission when they are not visible. The generated JavaScript handles this automatically:

Server-side validation matters: Always validate submitted data on your server, not just in the browser. A user could bypass client-side conditional logic by manipulating the form. Your server should enforce the same conditional rules: if the payment method is "PayPal," reject submissions that include credit card fields.

Accessibility Considerations

FormForge ensures that conditional fields are accessible to screen readers and keyboard users. When a field appears or disappears, the generated HTML uses ARIA live regions to announce the change. Specifically:

This behavior is built into the generated HTML. You do not need to add any additional ARIA attributes or JavaScript to make conditional forms accessible.

Performance: Keeping Conditional Forms Fast

FormForge evaluates conditions client-side using event listeners on the source fields. Each time a source field value changes, only the dependent fields are re-evaluated. This is efficient even for forms with dozens of conditional fields because the evaluation is targeted rather than global.

For forms with very complex dependency graphs (20 or more conditional rules), consider these optimization strategies:

Testing Conditional Forms

Testing every combination of conditions is critical. FormForge generates deterministic HTML for the same input, so you can write automated tests that verify the correct fields appear for each combination of source field values:

javascript
// Automated test: verify that credit card fields
// appear only when "Credit Card" is selected
import { test, expect } from "vitest";

test("credit card fields visible when Credit Card selected", async () => {
  const res = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${API_KEY}`,
    },
    body: JSON.stringify(paymentSchema),
  });

  const { html } = await res.json();

  // The HTML contains condition logic that will show/hide
  // fields based on the paymentMethod value
  expect(html).toContain("cardNumber");
  expect(html).toContain("bankName");
  expect(html).toContain("data-condition-field");
});

test("hidden fields have aria-hidden attribute", async () => {
  const { html } = await renderForm(paymentSchema);

  // Conditional fields start hidden until their
  // source field has a value
  expect(html).toContain('aria-hidden="true"');
});

Getting Started with Conditional Forms

Adding conditional logic to your FormForge forms requires no additional API calls or configuration. Simply add conditions arrays to any field in your schema, and the generated HTML handles the rest. Start with a simple two-option toggle (like the Individual/Business example above), verify it works, then layer on more complex conditions as needed.

For more details on the conditions syntax and supported operators, see the API documentation. To experiment with conditional forms interactively, try the live demo on the homepage.

Build smarter forms today

Get your free API key and add conditional logic to your forms in minutes.

Get Free API Key