Conditional logic lives on the builders themselves, not in ad-hoc component branches.
- Business rules stay attached to the field contract, next to its label, validation and default value
- The evaluated state is re-computed on every form change and exposed through the
visibilitymap, so the UI can still react outside the field renderer when needed - All helpers compose: multiple
visibleWhen*/requiredWhen*/disabledWhencalls on the same builder combine with AND logic - The
*Any(pairs)variants give you OR logic over a short list of field/value pairs; reach for a predicate when the rule spans more than a couple of fields - When a field becomes hidden, its value follows the on-hide policy (default: reset to the builder default)
| 1 | const schema = { |
| 2 | accountType: field.radio('Account type') |
| 3 | .options(['personal', 'business']) |
| 4 | .required(), |
| 5 | companyName: field.text('Company name') |
| 6 | .visibleAndRequiredWhen('accountType', 'business') |
| 7 | .clearOnHide(), |
| 8 | vatNumber: field.text('VAT number') |
| 9 | .visibleWhen('accountType', 'business') |
| 10 | .disabledWhen((values) => !values.companyName) |
| 11 | .clearOnHide(), |
| 12 | referral: field.text('Referral code') |
| 13 | .visibleWhenAny([ |
| 14 | ['source', 'friend'], |
| 15 | ['source', 'partner'], |
| 16 | ]) |
| 17 | .keepOnHide(), |
| 18 | } |
Visibility
Visibility helpers live on every builder. Multiple calls compose with AND logic - every rule must pass for the field to stay visible. When a field is hidden its value follows the on-hide policy (default: reset to the field default).
| Method | Type | Description |
|---|---|---|
visibleWhen(field, value?) | (field: string, value?: unknown) | Visible when the named field strictly equals value. Defaults value to true, so visibleWhen('acceptTerms') is the idiomatic toggle for a boolean gate. |
visibleWhen(predicate) | ((values, ctx) => boolean) | Cross-field predicate with access to the full form values snapshot. Use when the rule depends on more than one field. |
visibleWhenNot(field, value) | (field: string, value: unknown) | Visible when the named field is not strictly equal to value. Useful for "hide only on the default option" patterns. |
visibleWhenTruthy(field) | (field: string) | Visible when the named field is truthy. Matches anything that is not false, 0, '', null or undefined. |
visibleWhenFalsy(field) | (field: string) | Mirror of visibleWhenTruthy - visible when the named field is falsy. Handy for "still empty" hints or reminder blocks. |
visibleWhenAny(pairs) | (pairs: Array<[field: string, value: unknown]>) | Visible when at least one [field, value] pair matches (OR logic). Combine with plain visibleWhen(...) calls to express "any of A/B/C AND also X". |
visibleAndRequiredWhen(fieldOrPredicate, value?) | (field: string, value?: unknown) or ((values, ctx) => boolean) | Shorthand that pushes the same rule into both the visibility and required stacks in one call - keeps the two stacks in sync automatically. |
| 1 | field.text('Company name') |
| 2 | .visibleAndRequiredWhen('accountType', 'business') |
| 3 | .clearOnHide() |
Required
Dynamic required state works like visibility - the rule is evaluated on every form change. A hidden field never raises a required error even if requiredWhen would otherwise match.
| Method | Type | Description |
|---|---|---|
requiredWhen(field, value?) | (field: string, value?: unknown) | Required when the named field strictly equals value (default true). Multiple calls compose with AND logic. |
requiredWhen(predicate) | ((values, ctx) => boolean) | Cross-field predicate - use when the decision needs multiple fields. |
requiredWhenAny(pairs) | (pairs: Array<[field: string, value: unknown]>) | Required when any [field, value] pair matches (OR logic). |
| 1 | field.text('Referral code') |
| 2 | .requiredWhenAny([ |
| 3 | ['source', 'friend'], |
| 4 | ['source', 'partner'], |
| 5 | ]) |
Disabled
Disabled rules toggle the field interactivity without removing it from the layout. The field still submits its current value, it just cannot be edited by the user.
| Method | Type | Description |
|---|---|---|
disabledWhen(field, value?) | (field: string, value?: unknown) | Disabled when the named field strictly equals value (default true). Multiple calls compose with AND logic. |
disabledWhen(predicate) | ((values, ctx) => boolean) | Cross-field predicate form. There is intentionally no disabledWhenAny - compose a predicate if you need OR logic. |
| 1 | field.text('Invoice email') |
| 2 | .disabledWhen((values) => values.billingSameAsContact === true) |
Hidden value behavior
On-hide behavior controls what happens to a field value when the field becomes invisible. It only runs on the hide transition - re-showing the field never mutates the value.
| Method | Type | Description |
|---|---|---|
resetOnHide() | () => this | Reset the field to its builder default when it becomes hidden. This is the default if you never call any of these helpers. |
clearOnHide() | () => this | Clear the field on hide (empty string, null, or the empty form of the field type). Use it when a stale value would be confusing if the user toggles the gate back open. |
keepOnHide() | () => this | Keep the value as-is while hidden. Pairs well with persistence so the user never loses input when flipping a branching field back and forth. |
| 1 | // Default - reset |
| 2 | field.text('Company name').visibleWhen('accountType', 'business') |
| 3 | // Clear the input when the branch is not selected |
| 4 | field.text('VAT').visibleWhen('accountType', 'business').clearOnHide() |
| 5 | // Keep the value across visibility toggles |
| 6 | field.text('Notes').visibleWhen('showNotes').keepOnHide() |