Conditions
The affects package adds conditions to definitions: a field, a section or a step shows, hides, or hides and clears its value, by the values of other fields. A hidden field isn't validated, in every Formisch package and in a plain v.parse. Below you will learn how to write conditions and what they do.
Adding the extension
Conditions come from @whatsaaaa/form-builder-affects, an extension of the schema builder. Give the builder the extension:
import { affects } from '@whatsaaaa/form-builder-affects'
import { createSchemaBuilder } from '@whatsaaaa/form-builder-schema'
const builder = createSchemaBuilder({ extensions: [affects()] })Then write affects on the fields. Here, the VAT ID only shows, and is only required, for a business account:
{
"version": "1",
"fields": [
{
"name": "accountType",
"type": "radio",
"required": true,
"options": [
{ "value": "personal", "label": "Personal" },
{ "value": "business", "label": "Business" }
]
},
{
"name": "vatId",
"type": "text",
"required": true,
"affects": [
{
"effect": "hideAndClear",
"when": { "field": "accountType", "notEquals": "business" }
}
]
}
]
}const schema = builder.build(definition)
const personal = v.parse(schema, { accountType: 'personal' }) // { accountType: 'personal' }
const business = v.safeParse(schema, { accountType: 'business' }) // fails: vatId is requiredWithout the extension, affects is an unknown key: validate() reports it, and build() throws. A definition never runs without its conditions by mistake.
Effects
affects is a list of effects, each with the condition that applies it:
| Effect | While its condition is met |
|---|---|
show | the field shows. Otherwise it is hidden, like with hide |
hide | the field is hidden, and keeps its value |
hideAndClear | the field is hidden, and its value is cleared |
- Hide always wins. A field is hidden when its
showcondition isn't met, or itshideorhideAndClearcondition is. It is cleared only when itshideAndClearcondition is met. - Each effect goes once on a field. Join conditions with
alloranyinstead. A field can haveshowtogether withhideorhideAndClear. - Effects go on any field at any depth, display elements, groups and lists included, and on the layout's steps, sections and display elements. Not on a row, nor on the
offield of a list, which is the item itself: put them on the list.
Conditions
A condition tests one field, by its dot path, with exactly one operator. all and any join conditions, and nest to any depth:
{
"all": [
{ "field": "accountType", "equals": "business" },
{
"any": [
{ "field": "country", "in": ["DE", "AT"] },
{ "field": "vatId", "notEmpty": true }
]
}
]
}| Operator | Value | True when the field |
|---|---|---|
equals | a string, number or boolean | holds the value |
notEquals | a string, number or boolean | doesn't hold the value, or is empty |
in | a list of at least one | holds one of the values |
includes | a string, number or boolean | is a multiselect or a list of values, and holds the value |
empty | true | is empty |
notEmpty | true | isn't empty |
Paths are those of form rules: accountType, billing.zip, or lines.*.kind for the field of the same item of lines, from a field, a section or a display element inside its items. A path with an index, like lines.0.kind, or to an item's field from outside the items, is an issue.
Operators by kind
Each operator fits some kinds of fields:
| Field | equals, notEquals, in | includes | empty, notEmpty |
|---|---|---|---|
| text, number, checkbox, date, select, radio | yes | no | yes |
a multiselect, or a list of values with of | no | yes | yes |
a list of objects with fields, a file | no | no | yes |
| a group, which is never empty | no | no | no |
Values
A value must be one the field can hold: the field's schema, rules included, must take it, like a default. So { "field": "country", "equals": "Germny" } on a select is an issue, not a condition that is never met. A date is the ISO string the field holds, like "2026-11-09".
- Conditions read the input, what the form holds now. A number is a number, as renderers set it, but normalize rules like
trimaren't applied. - Empty is what
requiredtreats as empty:undefined,""or[]. An empty field neverequalsanything, andnotEqualsis true for it. - A checkbox nobody touched holds nothing, not
false. To show something while it isn't ticked, write{ "notEquals": true }, or give the checkbox"default": false.
What a hidden field does
- It isn't validated: no
required, no rules, and no form rule that reads it or is on it. Formisch shows no error on it. - It counts as empty in every other condition, so conditions chain. When
companyhides, a field shown forcompany.sizebeing"large"hides too, whatevercompany.sizeholds. - A field that
hideAndClearhides is blank. It submits nothing, a list submits[], and each field of a group is blank. The adapters also set it blank in the form as soon as it is cleared, so it is blank when it shows again. A field that starts hidden this way starts without itsdefault. - A field hidden otherwise keeps its value: by
hide, by ashowthat isn't met, or by a group, section or step thathidehides. It submits its output when its value is valid, and nothing when it isn't, so errors nobody can see never block the form.
So plain hiding submits old values. A product line that was a service line still submits its hours. Use hideAndClear for data that only makes sense while the field shows, and hide or show to keep what the user typed for when the field comes back. To clear a field that a show hides, give it hideAndClear with the opposite condition.
Groups, lists, sections and steps
- A hidden group, list, section or step hides everything in it, and clears it when
hideAndClearhides it. A field inside is hidden even when its ownshowis met. - A group whose fields are all hidden hides too. The fields that count are those with a value, and display elements with
affectsof their own. A list never hides this way: it has a value of its own, and its add button. - A section, a row or a step whose fields are all hidden hides by the same rule. A field hidden in a row drops out of it, and the other cells keep their spans. Next and Back skip a hidden step.
- A step or a section with
affectshides and clears the fields placed in it. Where the layout splits a group, only the fields of the group placed in the section go with it.
Loops
A condition can't depend on itself. Each of these is a cycle issue that names the loop:
ahides withb, andbwitha.- A field reads itself.
- A group reads a field inside it, like "show
companywhencompany.nameis filled". - A step or a section reads a field placed in it.
Rendering
The schema skips hidden fields, so validating and submitting need nothing more. The Vue and React adapters also leave hidden fields, sections and steps out of the form, and clear what hideAndClear clears: pass useJsonForm the builder with the extension.
To render conditions yourself, getVisibility() tells what is hidden for any input:
const visibility = builder.getVisibility(definition)
// The form's input now, e.g. Formisch's getInput(form).
const input = { accountType: 'personal', vatId: 'DE123456789' }
const state = visibility.of(input) // the same object until what is hidden changes
const [vatId] = builder
.getFieldTree(definition)
.filter((node) => node.field.name === 'vatId')
state.isHidden(vatId!, []) // true
const { toClear } = state // [{ path: ['vatId'], input: undefined }]: set it with Formisch's setInputstate.shownNodes(nodes, indexes) gives the layout nodes to render, and state.isStepHidden(step) the steps to skip. Every Formisch binding renderer uses it. Without the clearing, the output is still right, but a cleared field shows its old value when it comes back.
Checks and types
validate() checks every affects with the rest of the definition: shapes, paths, operators against the kind of the field they read, values and loops. See checking definitions. The package ships a JSON Schema with affects, and defineForm() types it. While the package is before 1.0, the hosted schema follows its latest release.