field typesselect

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:

  1. Static — choices come from optionConfigs (label + value pairs). An optional legacy options string array can mirror labels when you do not need distinct stored values.
  2. Dynamic — set isDynamic: true and provide dataSource so options load from an HTTP endpoint when the form opens.
  3. Dependent — same as dynamic, but dataSource.dependsOn references another field's id. After the parent has a value, the URL can include that value via parentValuePath placeholders, enabling multi-level cascading dropdowns.
  4. Nested forms — any entry in optionConfigs may include nestedForm with its own fields. Those inner fields validate and submit like top-level fields. A nested field may itself be a select with further nestedForm definitions, 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.

PropertyTypeDefaultDescription
idstringUnique field identifier referenced by dependent selects via dataSource.dependsOn.
typestring"select"Discriminator for this control.
labelstringHuman-readable label shown above the field or step heading.
requiredbooleanfalseWhen true, the form cannot submit without a value.
isHiddenbooleanfalseHides the field from respondents (may still store defaults).
isDisabledbooleanfalseRenders the field read-only.
instructionstringundefinedHelp text shown below the label (not the same as placeholder).
optionConfigsOptionConfig[]undefinedStatic choices as { label, value, nestedForm? }; nestedForm adds conditional child fields when this value is selected (recursive).
optionConfigs[].nestedFormNestedFormundefined{ id, name?, fields } — fields reuse full FormField shapes (including nested selects).
optionsstring[][]Simple label strings; values default to the same text if optionConfigs is omitted.
isDynamicbooleanfalseWhen true, fetch options using dataSource instead of optionConfigs.
dataSource.urlstringHTTP endpoint. Use placeholders such as {id} matching dataSource.parentValuePath segments.
dataSource.method"GET" | "POST"GETHTTP verb for the fetch.
dataSource.pathstring""Dot-path into JSON before mapping (e.g. quotes); empty means use the decoded JSON root.
dataSource.valueFieldstringProperty on each row used as the stored option value.
dataSource.labelFieldstringProperty on each row shown as the visible label.
dataSource.headersobjectundefinedExtra headers merged into the request.
dataSource.bodyobjectundefinedJSON body template for POST requests.
dataSource.dependsOnstringundefinedParent field id whose selected value gates fetching / URL substitution.
dataSource.parentValuePathstringundefinedToken matching a brace placeholder in url (parentValuePath "id" replaces "{id}" in the URL string).
dataSource.parentValueParamstringundefinedPOST-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