Open Form

When

Show fields and steps only while a condition on earlier answers holds.

<When> renders its children only while a condition holds. It's how a form branches: follow-up questions, skip logic, conditional steps. Don't use ternaries or useFormValues() to show or hide fields — the server can't see those, but it reads every <When>.

<Select name="role" label="Role" options={['Student', 'Engineer', 'Designer']} required />
<When field="role" equals="Student">
  <Text name="school" label="School" required />
</When>

Operators

Name the field with field, then add one or more operators. Every operator you add must hold. A <When field> with no operator always shows its children, and a <When> with none of field, all, any, not or condition stops the form from loading.

PropShows its children when the answer…
equalsequals the value. On a CheckboxGroup, exactly that one option is ticked.
notEqualsdiffers from the value, including while unanswered. On a CheckboxGroup, anything but exactly that one option, including nothing ticked.
inis one of the listed values. On a CheckboxGroup, at least one listed option is ticked.
notInis none of the listed values, including while unanswered. On a CheckboxGroup, none of the listed options is ticked.
containsincludes the option (CheckboxGroup), or contains the text (fields stored as a string). On a Select or RadioGroup that's a substring match too — contains="Eng" matches 'Engineer' — so use equals or in there.
filledhas an answer. filled={false} means it has none.
emptyhas no answer.
gt / gte / lt / lteis a number greater than / at least / less than / at most the value.
<When field="role" notEquals="Other">…</When>
<When field="role" in={['Engineer', 'Designer']}>…</When>
<When field="interests" contains="AI">…</When>
<When field="phone" filled>…</When>
<When field="nps" lte={6}>…</When>
<When field="age" gte={18} lt={65}>…</When>
<When field="agree" equals={true}>…</When>

"No answer" means blank text, an unchecked Checkbox, an empty CheckboxGroup or an untouched field. Values compare by their string form, so equals={1} matches a select option '1'. On a Checkbox, equals={true} (or notEquals={false}) matches a ticked box, and equals={false} (or empty) an unticked one, even if it was never touched. Number operators are false while the answer isn't a number.

A field's own defaultValue counts only once the field has shown: until its step comes up, conditions see no answer. For a default that conditions need from the start, use <Form defaultValues>.

Combining conditions

all, any and not take condition objects with the same keys — field plus operators — and nest freely:

<When all={[{ field: 'role', equals: 'Student' }, { field: 'country', equals: 'Taiwan' }]}>…</When>
<When any={[{ field: 'email', filled: true }, { field: 'phone', filled: true }]}>…</When>
<When not={{ field: 'plan', equals: 'free' }}>…</When>

condition takes one whole condition object, which helps when you build conditions in code and reuse them; type them as Condition, and <When>'s props as WhenProps, both exported from @open-form-build/core. Props given together on one <When> — for example field and any — must all hold.

Hidden fields

  • Required means required when shown. A hidden field isn't validated, so required inside <When> never blocks a respondent who can't see it.
  • Hidden answers are dropped on the server. The server works out which fields were visible for the submitted answers and keeps only those.
  • A hidden field counts as unanswered. Conditions only see the answers of fields that are showing, so whatever depends on a hidden field is re-checked as if it had no answer: <When field="phone" filled> hides its children, and <When field="phone" empty> shows them, even if phone was filled in before.
  • Answers are compared as they're stored. Text is trimmed and number fields are numbers, so equals="VIP" matches "VIP " and equals={5} matches 05.
  • The browser and the server agree. Both decide what's showing with the same code, so a field the respondent never saw is never required on submit.
  • The browser keeps them until submit. If someone switches back, their earlier answer is still there. Hooks like useFormValues() therefore still return hidden answers — check the same condition if a summary depends on it.

Earlier pages

A condition can read any field, including ones on earlier steps:

<Page title="Contact">
  <Select name="country" label="Country" options={['Taiwan', 'Japan', 'Other']} />
</Page>
<Page title="Shipping">
  <When field="country" equals="Taiwan">
    <Text name="postalCode" label="Postal code" required autoComplete="postal-code" />
  </When>
</Page>

Conditional steps

Wrap whole <Page>s to skip steps. A skipped step leaves the progress count, and Next goes straight to the next visible step.

<When field="role" equals="Engineer">
  <Page title="Work">
    <Text name="company" label="Company" required />
  </Page>
</When>

A <When> that wraps a <Page> holds only pages: a field or content next to the page inside it is never rendered or collected. When a <When> wraps only fields, put it inside a <Page>; at the top level of a multi-page form it would render above every step.

In Studio's Preview, the step list opens conditional pages directly, and hidden branches render with a note naming their condition, so you can check every path without filling the form in.

On this page