react-formbridge
Browse documentation
Core conceptsv1.0.2

Builder basics

Most builders share the same fluent inheritance tree, so the quickest way to read the API is to start with the common base surface and then add the builder-specific methods.

Inherited from BaseFieldBuilder:

MethodTypeDescription
defaultValue(value) => thisOverrides the default value set in the constructor.
required(message?) => thisMarks the field as required. A validation error is shown if the field is left empty.
optional() => thisMarks the field as optional (removes the required constraint). Useful when extending a schema where the field was previously required.
label(text) => thisOverrides the label set in the constructor.
placeholder(text?) => thisSets the placeholder text displayed inside the field when it is empty.
hint(text) => thisAdds a helper/hint text displayed below the field to guide the user.
disabled(value = true) => thisDisables (or re-enables) the field. A disabled field is rendered but not interactive.
hidden(value = true) => thisHides (or shows) the field. A hidden field is not rendered at all.
debounce(ms) => thisSets the debounce delay (in milliseconds) for value changes. Validation and side-effects are deferred until the user stops typing for the specified duration.
validate(fn) => thisAdds a custom validation function to the field. Multiple validators can be chained - they run in order and the first error message returned is displayed.
validateAsync(fn) => thisAlias for validate() when the intent is explicitly asynchronous. Useful for username availability checks, server-side uniqueness validation, promo code verification, and other async rules.
transform(fn) => thisRegisters a transform function that is applied to the field value before validation and submission (e.g. trimming whitespace, normalizing case).
render(fn) => thisProvides a custom render function to completely override the default field rendering. Use this when the built-in field components don't meet your UI needs.

Conditional logic helpers:

MethodTypeDescription
visibleWhen(fieldOrFn, value?) => thisMakes the field conditionally visible based on another field's value or a custom predicate function. Multiple visibleWhen* calls are combined with AND logic.
visibleAndRequiredWhen(fieldOrFn, value?) => thisMakes the field both visible and required when the condition is met. Shorthand for calling visibleWhen() and requiredWhen() with the same condition.
visibleWhenNot(field, value) => thisMakes the field visible when the referenced field does not equal the given value.
visibleWhenTruthy(field) => thisMakes the field visible when the referenced field has a truthy value (any value that is not false, 0, '', null, or undefined).
visibleWhenFalsy(field) => thisMakes the field visible when the referenced field has a falsy value (false, 0, '', null, or undefined).
visibleWhenAny(pairs) => thisMakes the field visible when any of the given field/value pairs match (OR logic).
requiredWhen(fieldOrFn, value?) => thisMakes the field conditionally required based on another field's value or a custom predicate function.
requiredWhenAny(pairs) => thisMakes the field required when any of the given field/value pairs match (OR logic).
disabledWhen(fieldOrFn, value?) => thisConditionally disables the field based on another field's value or a custom predicate function.
resetOnHide() => thisWhen the field becomes hidden (via a visibleWhen* condition), resets its value back to the default value.
keepOnHide() => thisWhen the field becomes hidden, keeps its current value intact. The value is preserved and will be included in form submission.
clearOnHide() => thisWhen the field becomes hidden, clears its value entirely (sets it to an empty/null state).

Inherited from StringFieldBuilder:

MethodTypeDescription
min(length, message?) => thisMinimum string length.
max(length, message?) => thisMaximum string length.
pattern(regex | regex[], message?) => thisRegex pattern or accepted regex alternatives.
patterns(regexes, message?) => thisAlias for pattern() when passing multiple accepted regex alternatives.
format(regex, message?) => thisInternal base format pattern used by built-in field presets like email/url/tel.
trim() => thisTrim value before validation / submit.
lowercase() => thisLowercase the value on every keystroke.
uppercase() => thisUppercase the value on every keystroke.
nonEmpty(message?) => thisRejects empty and whitespace-only strings (stricter than required()).
length(exact, message?) => thisRequires an exact character count for fixed-size codes.
between(min, max, message?) => thisShorthand for a combined min+max length check.
oneOf(values, message?) => thisRestricts accepted values to an allow-list.
notOneOf(values, message?) => thisBlocks values from a deny-list (reserved words, forbidden slugs, etc.).
matches(fieldName, message?) => thisMust match the value of another field (e.g. confirm password). Supports ref() paths.
sameAs(fieldName, message?) => thisAlias for matches() with more explicit semantics.

Special cases:

BuilderExtends base withNotes
field.select() / field.radio()options(...), optionsFrom(...), searchable(...)Picker-style builders
field.phone()defaultCountry(), storeE164(), country-aware phone helpersExtends base directly
field.file()Upload-focused surfaceMain exception - does not expose render(), transform(), or the conditional helpers from BaseFieldBuilder

Behavior vs styling

FormBridge keeps behavior and styling on separate layers. Pick the layer by the scope of the change, not the shape of the API:

LayerScopeWhere it lives
Business rules & reusable field behaviorTravels with the field definition everywhere it's renderedOn the builder - required(), validate(), transform(), visibleWhen(), render(), etc.
Shared visual themeApplies to every field / form / submit button at once and can react to live state, schema metadata, and platformuseFormBridge(schema, { globalDefaults(ctx) })
One-off styling exceptionsOverrides for a single render, not worth a builder or theme entryProps on the rendered <form.fields.*> component

Per-render styling props exposed on every generated field component:

  • Wrapper: style (cross-platform), className (web only)
  • Slot maps: styles (style slot map), classNames (web class slot map)
  • Slot props forwarding: wrapperProps, labelProps, hintProps, errorProps, inputProps, textareaProps, selectProps, searchInputProps
  • Render-hook escape hatches: renderLabel, renderHint, renderError, renderRequiredMark, renderPicker, renderOption, renderEmpty, renderLoading, renderFileIcon

Rule of thumb: if the same visual repeats across fields, move it to globalDefaults. If the rule describes what the field is (validation, normalization, visibility), it belongs on the builder. Local component props win over globalDefaults, so per-render overrides always take precedence.

Shared method examples

Mini examples for the shared base builder methods:

defaultValue('FR')
field.select('Country').defaultValue('FR')
required('Required') / optional()
field.text('Middle name').required('Required').optional()
label('Username') / placeholder('@alex') / hint('Shown publicly')
field.text('Handle').label('Username').placeholder('@alex').hint('Shown publicly')
disabled() / hidden() / debounce(500)
field.text('Referral code').disabled().hidden(false).debounce(500)
validate((value) => value ? null : 'Missing')
field.text('Company').validate((value) => value.length >= 2 ? null : 'Use at least 2 characters')
transform((value) => value.trim())
field.text('Slug').transform((value) => value.trim().toLowerCase().replace(/\s+/g, '-'))
render(fn)
field.custom(0).label('Rating').render(({ value, onChange }) => <Stars value={value} onChange={onChange} />)
visibleWhen('accountType', 'company')
field.text('Company name').visibleWhen('accountType', 'company')
visibleAndRequiredWhen('accountType', 'company')
field.text('Company name').visibleAndRequiredWhen('accountType', 'company')
visibleWhenNot('role', 'guest') / visibleWhenTruthy('hasVat') / visibleWhenFalsy('sameAsBilling')
field.text('VAT number').visibleWhenTruthy('hasVat')
visibleWhenAny([['role', 'admin'], ['role', 'manager']])
field.text('Internal note').visibleWhenAny([['role', 'admin'], ['role', 'manager']])
requiredWhen('needsInvoice') / requiredWhenAny([['country', 'FR'], ['country', 'DE']])
field.text('Tax ID').requiredWhenAny([['country', 'FR'], ['country', 'DE']])
disabledWhen('submitted')
field.text('Coupon code').disabledWhen('submitted')
resetOnHide() / keepOnHide() / clearOnHide()
field.text('Other').visibleWhen('reason', 'other').resetOnHide()
_build()
const descriptor = field.text('Debug').required()._build()

Mini examples for the shared string builder methods:

min(3) / max(20)
field.text('Username').trim().min(3).max(20)
pattern(/^[A-Z]{3}-\d{4}$/)
field.text('Partner code').pattern(/^[A-Z]{3}-\d{4}$/)
patterns([/^FR-/, /^DE-/], 'Use an EU code')
field.text('Region code').patterns([/^FR-/, /^DE-/], 'Use an EU code')
format(/^https:\/\/.+$/, 'Use HTTPS')
field.url('Webhook URL').format(/^https:\/\/.+$/, 'Use HTTPS')
trim() / lowercase() / uppercase()
field.email('Email').trim().lowercase()` and `field.masked('LL-999-LL').label('Plate').uppercase()
matches('password') / sameAs('password')
field.password('Confirm password').sameAs('password', 'Passwords must match')