webdevpuneet.com

Conditional Branching Form Fields — Show/Hide Sections Based on Prior Answers

Conditional Branching Form Fields — Show Only What Applies · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<form class="demo branch-form" id="branchForm">
  <fieldset class="f-group">
    <legend>Are you signing up as a business?</legend>
    <div class="radio-row">
      <label><input type="radio" name="accountType" value="personal" checked /> Personal</label>
      <label><input type="radio" name="accountType" value="business" /> Business</label>
    </div>
  </fieldset>

  <div class="branch-block" id="businessFields" hidden>
    <div class="f-row">
      <label for="companyName">Company name</label>
      <input type="text" id="companyName" name="companyName" placeholder="Acme Inc." />
    </div>
    <div class="f-row">
      <label for="teamSize">Team size</label>
      <select id="teamSize" name="teamSize">
        <option value="1-10">1–10</option>
        <option value="11-50">11–50</option>
        <option value="51+">51+</option>
      </select>
    </div>

    <fieldset class="f-group nested">
      <legend>Do you need an invoice?</legend>
      <div class="radio-row">
        <label><input type="radio" name="needsInvoice" value="yes" /> Yes</label>
        <label><input type="radio" name="needsInvoice" value="no" checked /> No</label>
      </div>
    </fieldset>

    <div class="branch-block nested" id="invoiceFields" hidden>
      <div class="f-row">
        <label for="vatId">VAT / Tax ID</label>
        <input type="text" id="vatId" name="vatId" placeholder="EU123456789" />
      </div>
      <div class="f-row">
        <label for="billingAddr">Billing address</label>
        <input type="text" id="billingAddr" name="billingAddr" placeholder="123 Main St, City" />
      </div>
    </div>
  </div>

  <button type="submit" class="submit-btn">Continue</button>
</form>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Two-level branching — a nested conditional section only reachable once its parent condition is also true
required attribute toggles in exact lockstep with visibility, preventing hidden fields from blocking submission
Hidden fields are cleared on collapse, guaranteeing stale data never leaks into the submitted payload
Collapsing a parent branch correctly cascades to collapse any nested branch inside it
Branch conditions are recomputed fresh from live form state on every change, never cached or stale
Subtle slide-in animation on newly revealed sections for a smoother reveal than an instant snap
Visually distinct nesting (indmaterial tone) makes the two branch levels easy to tell apart at a glance

About this UI Snippet

Conditional Branching Forms — Fields That Appear Only When Relevant

Screenshot of the Conditional Branching Form Fields — Show Only What Applies snippet rendered live

A long form that shows every possible field up front — regardless of whether it applies — asks users to mentally filter out irrelevant questions themselves. Conditional (or "branching") forms flip this: a field only appears once an earlier answer makes it relevant, and disappears again if that answer changes. This snippet implements a genuine two-level branch — a business/personal toggle reveals company fields, and a nested yes/no question inside that branch reveals invoice fields only when both conditions are true.

The core problem: required attributes and visibility must move together

The most common bug in hand-rolled conditional forms is a mismatch between what's *visible* and what's *required*. If required is hardcoded in the markup, a hidden field can silently block form submission the user can't see why is failing. If required is never toggled at all, a genuinely mandatory field (like company name for a business account) can be skipped entirely by switching back to personal after typing nothing. setBlockVisible() solves this by toggling .hidden and each field's .required property in the exact same function call — they can never drift out of sync because there is only one code path that changes either.

Clearing hidden fields prevents stale data from leaking into submission

When a branch collapses, setBlockVisible() also clears the value of every field inside it. Without this, a user could fill in a VAT ID, switch back to "no invoice needed," and — if the field were merely hidden rather than cleared — that stale VAT ID would still be present in FormData on submission, silently included in data that has nothing to do with what the user actually confirmed. Clearing on hide guarantees the submitted payload always reflects only what's currently visible and intentional.

Why the nested branch has to re-evaluate on its parent's change, not just its own

updateBusinessBranch() explicitly calls updateInvoiceBranch() whenever the *parent* toggle changes to personal — not because the invoice radio itself changed, but because collapsing the business section must also collapse anything nested inside it. Without this cascading call, switching from business-with-invoice back to personal would leave the invoice fields block technically still visible (since nothing directly told it to hide), floating disconnected from a parent section that no longer exists on screen.

A single source of truth for "is this field required right now"

Both updateBusinessBranch() and updateInvoiceBranch() independently recompute the *current* combined condition (isBusiness, and isBusiness && wantsInvoice) from the live radio state every time they run, rather than trusting a cached boolean from a previous call. This means the branching logic is always derived fresh from the form's actual current state, so it can't drift out of sync no matter which radio the user changes or in what order.

Step by step

How to Use

  1. 1
    Select "Business" on the first questionThe company name and team size fields slide into view, and company name becomes a required field.
  2. 2
    Switch to "Yes" on "Do you need an invoice?"A second, nested branch reveals the VAT ID and billing address fields — only reachable once the business branch is active.
  3. 3
    Switch back to "Personal"Both the business fields and the nested invoice fields collapse together, their values clear, and their required attributes turn off.
  4. 4
    Submit the formOnly fields relevant to the currently visible branches are validated and included in the submitted data — nothing hidden blocks or leaks through.
  5. 5
    Add your own branchesFollow the setBlockVisible() pattern: pair each conditional block with the list of field ids that should become required exactly when it is visible.

Real-world uses

Common Use Cases

Signup and onboarding forms
Personal vs business account types, or role-based onboarding where later questions depend on an earlier selection.
CHECKOUT
Billing and invoicing flows
Only ask for tax ID and billing address when the user actually needs a formal invoice.
SURVEY
Branching surveys and applications
Job applications, insurance quotes, and support tickets often need follow-up questions gated behind a prior answer.
SETTINGS
Progressive settings disclosure
Advanced settings panels where enabling a feature reveals its own sub-configuration fields.
Related: Styled Radio Buttons — CSS Only Plan Selector (No JavaScript)
See the Styled Radio Buttons — CSS Only Plan Selector (No JavaScript) for a related forms pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

It gets cleared. setBlockVisible() sets el.value = "" for every field in a branch the moment that branch is hidden, so switching answers back and forth never leaves stale, irrelevant data sitting in the form ready to be silently submitted.

A hidden-but-required field still blocks native form validation from succeeding, and the user has no visual explanation for why submission is failing. Toggling required in sync with visibility avoids both a false validation block and, in the opposite case, silently skipping a field that should have been mandatory.

updateBusinessBranch() explicitly calls updateInvoiceBranch() whenever the parent toggle changes, so the nested section always re-evaluates and collapses correctly even though its own radio input never fired a change event.

Follow the same pattern: give the new block its own required-fields array, write an update function that recomputes its condition from all relevant ancestor and own-level form state, and call that update function both from its own change listener and from its parent's update function so collapsing cascades correctly.

Yes — because required is only ever true on fields that are currently visible, the browser's native validation (and :invalid styling) only ever applies to fields the user can actually see and interact with.