Select (Dropdown)
Single-choice dropdowns with static optionConfigs (including recursive nested forms), API-driven options, or dependent/cascading fetches.
Overview
The Select field renders a dropdown where the respondent picks exactly one value. Common patterns:
- Static — choices come from
optionConfigs(label + value pairs). An optional legacyoptionsstring array can mirror labels when you do not need distinct stored values. - Dynamic — set
isDynamic: trueand providedataSourceso options load from an HTTP endpoint when the form opens. - Dependent — same as dynamic, but
dataSource.dependsOnreferences another field'sid. After the parent has a value, the URL can include that value viaparentValuePathplaceholders, enabling multi-level cascading dropdowns. - Nested forms — any entry in
optionConfigsmay includenestedFormwith its ownfields. Those inner fields validate and submit like top-level fields. A nested field may itself be a select with furthernestedFormdefinitions, so branching flows work recursively until an option stops chaining.
Published forms store answers keyed by each field's id, so dependent selects always reference parent fields by id, not by display label. For multiple selections per question, use the Multi-Select field; for broader HTTP patterns (POST body injection, query params), see Cascading Dropdowns.
Tip: Keep static lists short (roughly 7–10 visible choices) where possible; dynamic APIs scale better for long catalogs.
Properties
Note: In exported builder JSON, each field includes an id (UUID). Submission payloads use those ids as keys.
| Property | Type | Default | Description |
|---|---|---|---|
| id | string | — | Unique field identifier referenced by dependent selects via dataSource.dependsOn. |
| type | string | "select" | Discriminator for this control. |
| label | string | — | Human-readable label shown above the field or step heading. |
| required | boolean | false | When true, the form cannot submit without a value. |
| isHidden | boolean | false | Hides the field from respondents (may still store defaults). |
| isDisabled | boolean | false | Renders the field read-only. |
| instruction | string | undefined | Help text shown below the label (not the same as placeholder). |
| optionConfigs | OptionConfig[] | undefined | Static choices as { label, value, nestedForm? }; nestedForm adds conditional child fields when this value is selected (recursive). |
| optionConfigs[].nestedForm | NestedForm | undefined | { id, name?, fields } — fields reuse full FormField shapes (including nested selects). |
| options | string[] | [] | Simple label strings; values default to the same text if optionConfigs is omitted. |
| isDynamic | boolean | false | When true, fetch options using dataSource instead of optionConfigs. |
| dataSource.url | string | — | HTTP endpoint. Use placeholders such as {id} matching dataSource.parentValuePath segments. |
| dataSource.method | "GET" | "POST" | GET | HTTP verb for the fetch. |
| dataSource.path | string | "" | Dot-path into JSON before mapping (e.g. quotes); empty means use the decoded JSON root. |
| dataSource.valueField | string | — | Property on each row used as the stored option value. |
| dataSource.labelField | string | — | Property on each row shown as the visible label. |
| dataSource.headers | object | undefined | Extra headers merged into the request. |
| dataSource.body | object | undefined | JSON body template for POST requests. |
| dataSource.dependsOn | string | undefined | Parent field id whose selected value gates fetching / URL substitution. |
| dataSource.parentValuePath | string | undefined | Token matching a brace placeholder in url (parentValuePath "id" replaces "{id}" in the URL string). |
| dataSource.parentValueParam | string | undefined | POST-only: merges parent selection into the JSON body under this key. |
Static Select
Define choices explicitly with optionConfigs. Values such as option_1 are what gets submitted; labels are only for display. If you only provide options as strings, each stored value defaults to that label—fine for demos, but use optionConfigs whenever you need stable codes or nestedForm.
{
"id": "a1a3db88-8712-4003-96dd-d68175f4b326",
"type": "select",
"label": "static",
"required": true,
"isHidden": false,
"placeholder": "Enter select...",
"options": ["Option 1", "Option 2"],
"optionConfigs": [
{ "label": "Option 1", "value": "option_1" },
{ "label": "Option 2", "value": "option_2" },
{ "label": "Option 3", "value": "option_3" }
],
"isDisabled": false
}Dynamic Select
Set isDynamic and describe how to parse the HTTP response. The client drills into path, expects an array at that location, then maps each element using valueField / labelField.
{
"id": "4610d04b-840b-46a5-9476-44726ab77986",
"type": "select",
"label": "Dynamic",
"required": false,
"isHidden": false,
"placeholder": "Enter select...",
"options": ["Option 1", "Option 2"],
"isDynamic": true,
"dataSource": {
"url": "https://dummyjson.com/quotes",
"path": "quotes",
"valueField": "id",
"labelField": "author",
"method": "GET"
}
}Dependent Select
The dependent pattern layers on top of dynamic fetching: dependsOn must equal the parent select's id. When the parent's submitted value changes, every occurrence of {parentValuePath} inside url is replaced before the request fires. Until the parent has a value, dependent selects stay empty or prompt the respondent to finish the parent field first.
{
"id": "f54841b9-8e69-43e2-bc2b-3a38eddd469b",
"type": "select",
"label": "Dynamic with parent dependent",
"required": true,
"isHidden": false,
"placeholder": "Enter select...",
"options": ["Option 1", "Option 2"],
"isDynamic": true,
"dataSource": {
"url": "https://dummyjson.com/quotes/{id}",
"path": "",
"valueField": "id",
"labelField": "author",
"dependsOn": "4610d04b-840b-46a5-9476-44726ab77986",
"parentValuePath": "id"
},
"isDisabled": false
}Warning: The resolved JSON after applying path must be an array of objects so rows can map to label/value pairs. Single-object endpoints need wrapping or a different API contract.
Nested option forms
Add nestedForm on any static optionConfigs entry. When that option is selected, the renderer shows its nestedForm.fields beneath the dropdown (grouped under nestedForm.name when provided). Changing to another option swaps which nested branch is relevant.
There is no fixed depth limit in the schema: a nested select's options may again define nestedForm, which may contain another select, and so on. Validation walks the whole expanded tree using each field's id as submission keys—exactly like siblings at the root.
Note: nestedForm is authored alongside static option definitions; dynamically fetched rows do not automatically gain nested schemas unless your exported JSON attaches them.
{
"id": "727a220b-1a6d-4eb7-9dcb-3198eebbe9b5",
"type": "select",
"label": "Nested",
"required": false,
"isHidden": false,
"placeholder": "Enter select...",
"options": ["Option 1", "Option 2"],
"optionConfigs": [
{
"label": "Option 1",
"value": "option_1",
"nestedForm": {
"id": "5303a184-f332-4164-9c71-34979e1c9bdf",
"name": "",
"fields": [
{
"id": "8db4c00a-879b-4033-bb2a-045c2e331f9b",
"type": "text",
"label": "New Text Field",
"required": false,
"isHidden": false,
"placeholder": "Enter text..."
},
{
"id": "f841f563-bc4c-4884-92ee-aea09c640861",
"type": "select",
"label": "New Select Field",
"required": false,
"isHidden": false,
"placeholder": "Enter select...",
"options": ["Option 1", "Option 2"],
"optionConfigs": [
{
"label": "Option 1",
"value": "option_1",
"nestedForm": {
"id": "f5897029-6eff-4e27-af41-a01782abd9c5",
"name": "",
"fields": [
{
"id": "47515b14-34bd-437a-a9e9-822f844ca1a4",
"type": "select",
"label": "Inner nested select",
"required": false,
"isHidden": false,
"placeholder": "Enter select...",
"options": ["Option 1", "Option 2"],
"optionConfigs": [
{ "label": "Option 1", "value": "option_1" }
]
}
]
}
}
]
}
]
}
}
]
}Use Cases by Pattern
Static selects
- Finite enums shipped with the form: departments, tiers, ticket categories
- Compliance wording where labels differ from canonical codes stored in integrations
- Options that rarely change and must render offline without network calls
- Pilot UX strings mirrored in options[] while authoritative values live in optionConfigs
Dynamic selects
- Catalog data maintained elsewhere (authors, SKU lists, CRM entities)
- Quote-of-the-day style demos or lookup tables exposed through REST collections
- Large datasets where embedding optionConfigs would bloat the schema
- Environments where labels change frequently without republishing the form
Dependent / cascading selects
- Country → region → city drill-downs driven by hierarchical APIs
- Vehicle make/model/year chains where each tier filters the next endpoint
- Quote/detail workflows (parent picks summary row id, child loads detail arrays)
- Progressive disclosure: hide downstream selects via isHidden until prerequisites exist
Nested option forms
- Product type → SKU-specific attributes collected only when that branch applies
- Support categories where Tier 1 drives Tier 2 clarifying questions via nested selects
- Regulatory attestations that spawn jurisdiction-specific fields after each choice
- Recursive branching surveys without exposing irrelevant paths as conditional logic rules