Form Field Groups
A twenty-field form rendered as one flat column asks the submitter to read every input before understanding any of them. The same fields under three headings — "About you", "Your experience", "Why us" — become something a person can scan and answer a section at a time.
fieldGroups declares those headings. Each entry is a label plus the field names that belong under it:
forms:
- id: 1
name: apply
title: Apply for Senior Engineer
submitTo: { table: candidates }
fieldGroups:
- label: About you
fields: [first_name, last_name, email]
- label: Your experience
fields: [years_of_experience, github_url]
fields:
- { kind: table-field, column: first_name, required: true }
- { kind: table-field, column: last_name, required: true }
- { kind: table-field, column: email, required: true }
- { kind: standalone, name: years_of_experience, inputType: number }
- { kind: standalone, name: github_url, inputType: url }Group Properties
| Property | Description | Default |
|---|---|---|
label |
Section heading rendered above the group's fields. Must be at least one character. | Required. |
fields |
Field names belonging to this section, in the order they should render. At least one required. | Required. |
visibleWhen |
Condition gating the whole section. Same shape and operators as a field's visibleWhen. |
Always visible. |
fieldGroups is itself optional. Omit it and the form renders fields[] top to bottom with no headings — which is the right choice for a form short enough not to need signposting. When present, the array must declare at least one group.
Order Comes From the Groups
Once fieldGroups exists, it drives layout: sections render in array order, and each field renders in the position its group gives it. That means grouping can reorder a form without touching fields[] — the fields[] array keeps defining what the fields are, the groups define where they appear.
A field you never list still renders. It falls through to the bottom, after every section, in fields[] source order. Grouping half a form is therefore valid: the named fields get headings, the rest trail behind as an unlabeled block.
Conditional Sections
visibleWhen gates an entire section — label and every field under it — on the value of another field, using the same condition primitive as field-level rules:
fieldGroups:
- label: Preferences
fields: [wants_relocation]
- label: Relocation
fields: [relocation_country, relocation_date]
visibleWhen: { field: wants_relocation, operator: eq, value: true }The rule is evaluated server-side. On first render there are no answers yet, so a conditional section starts hidden and appears once its trigger is answered. On submit the rule is evaluated again, this time against the submitted values, and a section whose rule is false is treated as if it were never part of the form: its required fields do not block the submission.
A hidden section drops its values, it does not merely skip validation. When a group's visibleWhen is false at submit time, every field listed under it is stripped from the payload before the table write and the ledger write. A value the submitter typed while the section was open — then hid again by changing the trigger — is not persisted. Never put a field you always need inside a conditional group.
Groups Versus Steps
Both carve a form into parts, and both take a visibleWhen. The difference is what the submitter sees at once.
| Use | When |
|---|---|
fieldGroups |
All fields stay on one page and one scroll; headings give structure. Requires layout: single-page (the default). |
steps |
Fields are split across screens with prev/next navigation and per-step validation. Requires layout: multi-step or one-question. |
Related Pages
- Form Fields — the
fields[]entries a group refers to by name. - Conditional Logic — the operator vocabulary
visibleWhendraws on. - Multi-Step Forms —
steps[], the multi-screen alternative to groups. - Forms Overview — where
fieldGroupssits in the full form schema.
Last updated September 1, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.