Single-choice picker for finite option lists.
- Use
options(list)for local options - Use
optionsFrom(fetcher, config)for remote datasets - Add
searchable()when a richer lookup experience is needed - Use
defaultSelected(...)/selected(...)when the form should open on a meaningful choice instead of an empty placeholder
Select.web.tsxtsx
| 1 | const schema = { |
| 2 | workspace: field.select('Workspace') |
| 3 | .options([ |
| 4 | { label: 'Operations workspace', value: 'ops' }, |
| 5 | { label: 'Revenue cockpit', value: 'revenue' }, |
| 6 | { label: 'Support command center', value: 'support' }, |
| 7 | ]) |
| 8 | .defaultSelected('revenue') |
| 9 | .required(), |
| 10 | } |
optionsFrom config
keycacheTtl(default60000)debounce(default300)minChars(default0)dependsOn(default[])initialOptions(default[])fetchOnMount(defaulttrue)keepPreviousOptions(defaulttrue)preserveOnError(defaulttrue)
Custom picker modal
Need a custom modal, bottom sheet, command palette, or searchable dialog instead of the built-in picker? Pass renderPicker.
- Works for local options and
optionsFrom(...) - Works per rendered field through
<fields.city renderPicker={renderPicker} /> - If the built-in trigger itself should disappear too, keep the same select schema field and move to
form.fieldController(name)
InlineExample-2.tsxtsx
| 1 | const schema = { |
| 2 | city: field |
| 3 | .select('City') |
| 4 | .optionsFrom(fetchCities, { |
| 5 | key: 'city-search', |
| 6 | debounce: 250, |
| 7 | minChars: 2, |
| 8 | }) |
| 9 | .searchable(), |
| 10 | } |
| 11 | |
| 12 | const form = useFormBridge(schema) |
| 13 | |
| 14 | <form.Form onSubmit={save}> |
| 15 | <form.fields.city |
| 16 | renderPicker={({ |
| 17 | open, |
| 18 | search, |
| 19 | setSearch, |
| 20 | options, |
| 21 | loading, |
| 22 | error, |
| 23 | triggerLabel, |
| 24 | closePicker, |
| 25 | selectOption, |
| 26 | }) => |
| 27 | open ? ( |
| 28 | <CityLookupModal |
| 29 | title={triggerLabel} |
| 30 | query={search} |
| 31 | loading={loading} |
| 32 | error={error} |
| 33 | items={options} |
| 34 | onQueryChange={setSearch} |
| 35 | onClose={closePicker} |
| 36 | onSelect={(option) => selectOption(option)} |
| 37 | /> |
| 38 | ) : null} |
| 39 | /> |
| 40 | </form.Form> |
The renderPicker context gives you: open, search, setSearch, clearSearch, options, loading, error, selectedOption, triggerLabel, openPicker, closePicker, and selectOption. That makes it easy to plug the same field into a design-system modal on web, a native sheet on mobile, or a fully custom async search experience without replacing the rest of the field API.
Defaults, inheritance & field methods
- defaultValue is
'' - type is
select - Selected values are stored as the option
value, so bothstringandnumberare supported - Shared methods: see Base field builder
- String-specific helpers are not available on select fields
- Custom picker UIs are wired through per-field
renderPickeror fully customform.fieldController(name)flows
Select-specific methods:
| Method | Type | Description |
|---|---|---|
options(list) | string[] | { label, value }[] | Loads local options from plain strings or explicit label/value objects. |
optionsFrom(fetcher, config) | fetcher + async config | Loads remote options with debounce, cache, dependency tracking, and loading state support. |
searchable(value = true) | value?: boolean | Enables search-oriented picker behavior. |
defaultSelected(valueOrOption) | value | option object | Pre-selects an option by raw value or option object. |
selected(valueOrOption) | value | option object | Alias for defaultSelected() with more explicit wording. |
oneOf(values, message?) | Array<value | option> | Additional allow-list check on top of options(). Useful when the option list is broader than what the current form should actually accept (e.g. gating promo tiers by user role). |
notOneOf(values, message?) | Array<value | option> | Deny-list version of oneOf() - rejects a subset of values even if they exist in options(). |
disallowPlaceholder(message?) | message?: string | Shorthand for required() with a select-oriented default message ("Please select an option."). Use it when the placeholder row is visible but must not be submitted. |
Recipes
Patterns that showcase select-specific strengths.
Preselected workspace
Workspace.tsxtsx
| 1 | const WORKSPACE_OPTIONS = [ |
| 2 | { label: 'Operations workspace', value: 'ops' }, |
| 3 | { label: 'Revenue cockpit', value: 'revenue' }, |
| 4 | { label: 'Support command center', value: 'support' }, |
| 5 | ] |
| 6 | |
| 7 | const schema = { |
| 8 | workspace: field.select('Workspace') |
| 9 | .options(WORKSPACE_OPTIONS) |
| 10 | .defaultSelected('revenue') |
| 11 | .required(), |
| 12 | } |
Numeric option values
SeatPack.tsxtsx
| 1 | const schema = { |
| 2 | seatPack: field.select('Seat pack') |
| 3 | .options([ |
| 4 | { label: 'Starter · 5 seats', value: 5 }, |
| 5 | { label: 'Growth · 12 seats', value: 12 }, |
| 6 | { label: 'Scale · 25 seats', value: 25 }, |
| 7 | ]) |
| 8 | .defaultSelected(12), |
| 9 | } |
Async city search with debounce
CitySearch.tsxtsx
| 1 | const schema = { |
| 2 | city: field.select('City') |
| 3 | .optionsFrom(fetchCities, { |
| 4 | key: 'city-search', |
| 5 | debounce: 250, |
| 6 | minChars: 2, |
| 7 | }) |
| 8 | .searchable(), |
| 9 | } |
Dependent cascading selects
RegionCascade.tsxtsx
| 1 | const schema = { |
| 2 | country: field.select('Country').options(COUNTRIES).required(), |
| 3 | region: field.select('Region') |
| 4 | .optionsFrom(fetchRegions, { |
| 5 | key: 'regions', |
| 6 | dependsOn: ['country'], |
| 7 | }), |
| 8 | } |
Fully custom trigger
CustomTrigger.tsxtsx
| 1 | const schema = { |
| 2 | routingMode: field.select('Routing mode') |
| 3 | .options(ROUTING_MODES), |
| 4 | } |
| 5 | |
| 6 | const form = useFormBridge(schema) |
| 7 | const controller = form.fieldController('routingMode') |
| 8 | |
| 9 | <form.Form onSubmit={save}> |
| 10 | <MyTrigger |
| 11 | label={controller.value ?? 'Select mode'} |
| 12 | onClick={controller.openPicker} |
| 13 | /> |
| 14 | <MyModal |
| 15 | open={controller.isOpen} |
| 16 | options={controller.options} |
| 17 | onSelect={controller.setValue} |
| 18 | onClose={controller.closePicker} |
| 19 | /> |
| 20 | </form.Form> |