react-formbridge
Browse documentation
Field buildersv1.0.2

field.select()

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
1const 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

  • key
  • cacheTtl (default 60000)
  • debounce (default 300)
  • minChars (default 0)
  • dependsOn (default [])
  • initialOptions (default [])
  • fetchOnMount (default true)
  • keepPreviousOptions (default true)
  • preserveOnError (default true)

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
1const 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
12const 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 both string and number are supported
  • Shared methods: see Base field builder
  • String-specific helpers are not available on select fields
  • Custom picker UIs are wired through per-field renderPicker or fully custom form.fieldController(name) flows

Select-specific methods:

MethodTypeDescription
options(list)string[] | { label, value }[]Loads local options from plain strings or explicit label/value objects.
optionsFrom(fetcher, config)fetcher + async configLoads remote options with debounce, cache, dependency tracking, and loading state support.
searchable(value = true)value?: booleanEnables search-oriented picker behavior.
defaultSelected(valueOrOption)value | option objectPre-selects an option by raw value or option object.
selected(valueOrOption)value | option objectAlias 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?: stringShorthand 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
1const WORKSPACE_OPTIONS = [
2 { label: 'Operations workspace', value: 'ops' },
3 { label: 'Revenue cockpit', value: 'revenue' },
4 { label: 'Support command center', value: 'support' },
5]
6
7const schema = {
8 workspace: field.select('Workspace')
9 .options(WORKSPACE_OPTIONS)
10 .defaultSelected('revenue')
11 .required(),
12}

Numeric option values

SeatPack.tsxtsx
1const 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
1const 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
1const 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
1const schema = {
2 routingMode: field.select('Routing mode')
3 .options(ROUTING_MODES),
4}
5
6const form = useFormBridge(schema)
7const 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>