react-formbridge
Browse documentation
Field buildersv1.0.2

field.phone()

Country-aware phone builder with flag selector, dial codes, format validation, and E.164 storage.

  • For a basic phone text input without country metadata, use field.tel() instead
  • The renderer shows a country picker with flags, dial codes, and optional search
  • storeE164() normalizes the output to +33612345678 format for API consumption
import { useState } from 'react'
import { field, useFormBridge } from '@runilib/react-formbridge'

const schema = {
  phone: field.phone('Phone')
    .defaultCountry('FR')
    .preferredCountries(['FR', 'US', 'GB'])
    .searchable()
    .showFlag(true)
    .showDialCode(true)
    .storeE164()
    .required(),
}

export function PhonePlaygroundWeb() {
  const [submitted, setSubmitted] = useState<Record<string, unknown> | null>(null)
  const { Form, fields, state } = useFormBridge(schema, {
    validateOn: 'onBlur',
  })

  return (
    <div
      style={{
        fontFamily: 'sans-serif',
        padding: 20,
        background: '#f5f7fb',
        display: 'grid',
        gap: 16,
      }}
    >
      <div>
        <h3 style={{ margin: '0 0 8px' }}>Interactive phone field</h3>
        <p style={{ margin: 0, color: '#4b5563' }}>
          Switch country, type a number, then submit to inspect the E.164 payload.
        </p>
      </div>

      <Form
        onSubmit={async (values) => {
          setSubmitted(values)
        }}
      >
        <div style={{ display: 'grid', gap: 12 }}>
          <fields.phone />
          <Form.Submit>Save phone</Form.Submit>
        </div>
      </Form>

      <div style={{ display: 'grid', gap: 12 }}>
        <div
          style={{
            border: '1px solid #d6d9e0',
            borderRadius: 12,
            padding: 12,
            background: '#fff',
          }}
        >
          <strong>Live values</strong>
          <pre style={{ marginBottom: 0, whiteSpace: 'pre-wrap' }}>
            {JSON.stringify(state.values, null, 2)}
          </pre>
        </div>

        <div
          style={{
            border: '1px solid #d6d9e0',
            borderRadius: 12,
            padding: 12,
            background: '#fff',
          }}
        >
          <strong>Last submit</strong>
          <pre style={{ marginBottom: 0, whiteSpace: 'pre-wrap' }}>
            {JSON.stringify(submitted, null, 2)}
          </pre>
        </div>
      </div>
    </div>
  )
}

export default PhonePlaygroundWeb

Defaults, inheritance & field methods

  • defaultValue is null
  • type is phone
  • placeholder defaults to 'Enter phone number'
  • debounce defaults to 0 for immediate formatting feedback
  • default country is 'FR', preferred countries default to ['FR', 'US', 'GB', 'DE', 'ES']
  • Shared methods: see Base field builder

Phone-specific methods:

MethodTypeDescription
defaultCountry(code)code: stringSets the initial selected country.
preferredCountries(codes)codes: string[]Shortlists countries shown at the top of the picker.
searchable(value = true)value?: booleanEnables country search inside the picker.
showFlag(value = true)value?: booleanShows or hides the country flag.
showDialCode(value = true)value?: booleanShows or hides the dial code prefix.
countryLayout(layout)'integrated' | 'detached'Chooses whether the country selector sits inside the same bordered shell as the input (integrated, default) or as a separate button before it (detached). Drives both the default styling and a data-fb-layout attribute you can target in CSS.
storeE164()() => thisStores a normalized E.164 string (e.g. +33612345678) instead of the richer { country, national, e164 } phone payload. Use this when the backend expects a flat string.
validateFormat(value = true)value?: booleanEnables libphonenumber-based format validation. Adds three sequential checks: isPossible(), isValid(), and a format parse - each with its own error message. Disable with validateFormat(false) if you need lenient input.

Per-render props (text, render, passthroughs)

Beyond the builder methods above, every <fields.phone /> renderer accepts props to customize copy, swap rendering, or forward attributes to the underlying inputs. Pass them directly on the rendered field:

InlineExample-1.tsxtsx
1<fields.phone
2 searchPlaceholderText="Search a country…"
3 emptySearchText={({ search }) => `No match for "${search}"`}
4 renderCountryItemContent={({ country, defaultContent, selected }) => (
5 <>
6 {defaultContent}
7 {selected ? <span></span> : null}
8 </>
9 )}
10 inputProps={{ 'aria-describedby': 'phone-help' }}
11/>

Text overrides — each accepts a plain string or a function that receives the render context (current country, open state, search query, …).

MethodTypeDescription
countryButtonAriaLabelstring | (ctx) => stringAccessibility label for the country-picker trigger button. Receives the current render context (current country, open state, search, …).
searchPlaceholderTextstring | (ctx) => stringPlaceholder for the country-search input inside the picker. Defaults to 'Search country…'.
emptySearchTextstring | (ctx) => stringText shown when the search query matches no countries. Defaults to 'No countries match "<query>".' / 'No countries available.'.
e164Textstring | (ctx & { e164 }) => stringOpt-in label rendered alongside the E.164 preview. Only shown when e164Text or renderE164 is provided.

Render overrides - each receives a defaultContent node so you can wrap the built-in UI or replace it entirely.

MethodTypeDescription
renderCountryButtonContent(ctx & { defaultContent }) => ReactNodeFull renderer for the country-picker trigger content (flag, dial code, chevron). defaultContent contains the built-in layout so you can wrap it.
renderCountryItemContent(ctx & { country, defaultContent, index, selected }) => ReactNodeFull renderer for a single row in the country list. Useful to add a "selected" checkmark or a custom layout.
renderEmptySearchContent(ctx & { defaultContent }) => ReactNodeFull renderer for the "no results" state shown inside the picker.
renderE164(ctx & { defaultContent, e164 }) => ReactNodeOpt-in full renderer for the E.164 preview below the input. Only shown when e164Text or renderE164 is provided.
renderLabel / renderHint / renderError / renderRequiredMark(ctx) => ReactNodeInherited from every field - replace the label, hint, error, or required-mark rendering entirely.

Passthrough props & layout

MethodTypeDescription
inputPropsWeb: InputHTMLAttributes · Native: TextInputPropsPassthrough attributes for the phone-number input itself (aria-*, data-*, onKeyDown, testID, …).
searchInputPropsWeb: InputHTMLAttributes · Native: TextInputPropsPassthrough attributes for the country-search input inside the picker.
wrapperProps / labelProps / hintProps / errorPropsPlatform-native propsPassthrough props for the wrapper, label, hint, and error elements. Each merges its style with the default.
hideLabelbooleanHides the visible label while keeping the accessible name via aria-label / accessibilityLabel.
countryLayout'integrated' | 'detached'Same as the builder method but at the render site. Takes precedence over the schema value for this specific render.
stylesRecord of slot → stylePer-slot style overrides (see the Style keys section below). Each slot is merged over the built-in default, so you can tweak a single property or replace it entirely.

Style keys

The styles prop accepts an object keyed by renderer slot. Each entry is merged over the built-in default for that slot, so you can tweak one property or replace it.

InlineExample-1.tsxtsx
1<fields.phone
2 styles={{
3 phoneModalCard: { borderRadius: 20, padding: 16 },
4 phoneCountryRow: { paddingVertical: 16 },
5 phoneCountryName: { fontWeight: '600' },
6 }}
7/>

Native slots (ViewStyle / TextStyle)

MethodTypeDescription
wrapperViewStyleOuter field container.
labelTextStyleField label.
requiredMarkTextStyleThe asterisk shown next to required labels.
hintTextStyleHelper text below the input.
errorTextStyleError text shown below the input.
phoneRowViewStyleRow wrapping the country button + phone input.
phoneCountryButtonViewStyleThe picker trigger button.
phoneCountryFlagTextStyleFlag emoji inside the trigger and country rows.
phoneCountryDialTextStyleDial-code text (e.g. +33).
phoneCountryDividerViewStyleVertical divider between the country button and input in the integrated layout.
phoneChevronTextStyleChevron glyph inside the trigger.
phoneInputTextStyleThe phone-number TextInput.
phoneE164TextStyleThe opt-in E.164 preview text.
phoneModalBackdropViewStyleDimmed overlay behind the country picker.
phoneModalCardViewStyleThe modal card that contains the country list.
phoneSearchInputTextStyleCountry-search input inside the picker.
phoneCountryRowViewStyleOne row in the country list.
phoneCountryNameTextStyleCountry name inside a row.
phoneSeparatorViewStyleThe separator between preferred countries and the rest of the list.
phoneEmptyTextTextStyleEmpty-state text when no countries match.

Web slots (CSSProperties)

MethodTypeDescription
wrapperCSSPropertiesOuter field container.
labelCSSPropertiesField label.
requiredMarkCSSPropertiesThe asterisk shown next to required labels.
hintCSSPropertiesHelper text below the input.
errorCSSPropertiesError text shown below the input.
phoneRowCSSPropertiesRow wrapping the country button + phone input.
phoneCountryButtonCSSPropertiesThe picker trigger button.
phoneCountryFlagCSSPropertiesFlag glyph inside the trigger and country rows.
phoneCountryDialCSSPropertiesDial-code text (e.g. +33).
phoneCountryDividerCSSPropertiesVertical divider between the country button and input in the integrated layout.
phoneChevronCSSPropertiesChevron glyph inside the trigger.
phoneInputCSSPropertiesThe phone-number <input>.
phoneE164CSSPropertiesThe opt-in E.164 preview text.
phoneSearchWrapperCSSPropertiesWrapper around the country-search input.
phoneSearchInputCSSPropertiesCountry-search <input>.
phoneCountryListCSSPropertiesOuter container of the country list popup.
phoneCountryScrollCSSPropertiesScrollable container holding the country rows.
phoneCountryItemCSSPropertiesOne row in the country list.
phoneCountryNameCSSPropertiesCountry name inside a row.
phoneSeparatorCSSPropertiesThe separator between preferred countries and the rest of the list.
phoneEmptyTextCSSPropertiesEmpty-state text when no countries match.

Recipes

Patterns that showcase phone-specific strengths.

International signup

InternationalSignup.tsxtsx
1const schema = {
2 phone: field.phone('Phone')
3 .defaultCountry('FR')
4 .preferredCountries(['FR', 'US', 'GB'])
5 .searchable()
6 .showFlag(true)
7 .showDialCode(true),
8}

API-ready E.164 output

E164Phone.tsxtsx
1const schema = {
2 phone: field.phone('Phone')
3 .storeE164()
4 .required()
5 .validateFormat(true),
6}

US-only customer support

UsSupport.tsxtsx
1const schema = {
2 phone: field.phone('Phone')
3 .defaultCountry('US')
4 .preferredCountries(['US'])
5 .showDialCode(false)
6 .validateFormat(),
7}

Disabled display field

SupportLine.tsxtsx
1const schema = {
2 supportLine: field.phone('Support line')
3 .defaultCountry('US')
4 .disabled()
5 .hint('Managed by your account team'),
6}