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:
{
"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:
- equals — Field value exactly matches the specified value
- not_equals — Field value does not match the specified value
- contains — Field value contains the specified substring (useful for text fields)
- greater_than — Numeric field value is greater than the specified number
- less_than — Numeric field value is less than the specified number
- is_empty — Field has no value (the
valueproperty is ignored) - is_not_empty — Field has any value
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:
{
"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.
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:
{
"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:
{
"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:
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, '"')}" 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:
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:
// 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:
- When a field's conditions evaluate to
false, the field is hidden withdisplay: none - The field's
requiredattribute is temporarily removed - The field's value is cleared to prevent stale data from being submitted
- When the conditions become
trueagain, the field reappears and its validation rules are restored
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:
- Hidden fields use
aria-hidden="true"in addition todisplay: none, ensuring screen readers skip them entirely - When new fields appear, an
aria-live="polite"region announces the new content without interrupting the user's current focus - Tab order is automatically updated so keyboard users skip hidden fields and move to the next visible input
- Error messages for conditional fields are also hidden when the field is not visible, preventing confusing announcements
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:
- Flatten deep chains — Instead of A depends on B which depends on C, have both B and C depend directly on A with compound conditions
- Group related conditions — Use
conditionalSectionsto show or hide groups of fields together instead of individual field conditions - Split into wizard steps — If a form has more than 15 conditional rules, consider breaking it into a multi-step wizard where each step has simpler logic
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:
// 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