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>

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

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.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to explain precisely why clearing a hidden field's value and toggling its required attribute both matter for correct form semantics, and what specific bugs would appear if either step were skipped. It's also worth asking for a version driven by a declarative rules object (mapping field ids to the parent conditions that make them visible/required) instead of hand-written branch functions, which scales better past two or three conditional levels.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a form in HTML, CSS, and vanilla JavaScript with two levels of conditional branching — no framework, no external library.

Requirements:
- A radio group asking whether the account is Personal or Business. Selecting Business reveals a section with a company name field (required only while visible) and a team size dropdown.
- Inside that business section, a second nested radio group asks whether the user needs an invoice. Selecting Yes reveals a further nested section with a VAT ID and billing address field, each required only while visible — this nested section must only ever be reachable while the business section itself is also visible.
- Toggling any field's visibility must toggle its required attribute in the exact same operation, so a hidden field can never block submission and a visible mandatory field is always genuinely required.
- Clear the value of any field the instant its containing section becomes hidden, so switching answers back and forth never leaves stale data from a previous answer sitting in the form.
- Collapsing the business section (by switching back to Personal) must also correctly collapse the nested invoice section if it was open, even though the invoice radio itself did not change.
- On submit, prevent the default page reload and show the collected form data, confirming that hidden fields are correctly absent from the submitted payload.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

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.