Skip to main content

Design system components

PhoneInput

International phone field: country selector + formatted number input with field chrome.

Status: stable. Group: form. Markdown version: /design-system/components/phone-input.md

When to use

PhoneInput wraps react-phone-number-input with the design-system field chrome (FieldShell: label, status message, helper text and label-row actions) and token-based styling over the library's country selector and input. It formats as you type and emits the E.164 value (or undefined while incomplete) through onChange. The default country is FI; override defaultCountry per audience.

When not to use

  • Do not bypass onChange and read the DOM input value; it is the display format, not the E.164 value.
  • Do not use TextInput type=tel for international audiences; you lose country selection and normalization.

Look-alikes

  • TextInput: Do not use TextInput type=tel for international audiences; you lose country selection and normalization.

Props

PhoneInput props
PropTypeDefaultDescriptionRequired
labelstringnoneLabel text.Yes
valuestringnonePhone number value (E.164 format)No
errorstringnoneError message; renders above the helper line.No
statusFieldStatusnoneSemantic state: colours the border and styles statusMessage. error overrides it.No
statusMessagestringnoneMessage under the field for status, above the helper line.No
actionsFieldAction[]noneIcon buttons at the end of the label row: each runs an operation or opens a menu.No
helperTextstringnoneHelper text below the input.No
placeholderstringnonePlaceholder text.No
disabledbooleanfalseDisabled state.No
requiredbooleanfalseShows the required marker on the label.No
idstringnoneExplicit control id (wires the label); auto-generated when omitted.No
defaultCountry"AC" | "AD" | "AE" | "AF" | "AG" | "AI" | "AL" | "AM" | "AO" | "AR" | "AS" | "AT" | "AU" | "AW" | "AX" | "AZ" | "BA" | "BB" | "BD" | "BE" | "BF" | "BG" | "BH" | "BI" | "BJ" | "BL" | "BM" | "BN" | "BO" | "BQ" | "BR" | "BS" | "BT" | "BW" | "BY" | "BZ" | "CA" | "CC" | "CD" | "CF" | "CG" | "CH" | "CI" | "CK" | "CL" | "CM" | "CN" | "CO" | "CR" | "CU" | "CV" | "CW" | "CX" | "CY" | "CZ" | "DE" | "DJ" | "DK" | "DM" | "DO" | "DZ" | "EC" | "EE" | "EG" | "EH" | "ER" | "ES" | "ET" | "FI" | "FJ" | "FK" | "FM" | "FO" | "FR" | "GA" | "GB" | "GD" | "GE" | "GF" | "GG" | "GH" | "GI" | "GL" | "GM" | "GN" | "GP" | "GQ" | "GR" | "GT" | "GU" | "GW" | "GY" | "HK" | "HN" | "HR" | "HT" | "HU" | "ID" | "IE" | "IL" | "IM" | "IN" | "IO" | "IQ" | "IR" | "IS" | "IT" | "JE" | "JM" | "JO" | "JP" | "KE" | "KG" | "KH" | "KI" | "KM" | "KN" | "KP" | "KR" | "KW" | "KY" | "KZ" | "LA" | "LB" | "LC" | "LI" | "LK" | "LR" | "LS" | "LT" | "LU" | "LV" | "LY" | "MA" | "MC" | "MD" | "ME" | "MF" | "MG" | "MH" | "MK" | "ML" | "MM" | "MN" | "MO" | "MP" | "MQ" | "MR" | "MS" | "MT" | "MU" | "MV" | "MW" | "MX" | "MY" | "MZ" | "NA" | "NC" | "NE" | "NF" | "NG" | "NI" | "NL" | "NO" | "NP" | "NR" | "NU" | "NZ" | "OM" | "PA" | "PE" | "PF" | "PG" | "PH" | "PK" | "PL" | "PM" | "PR" | "PS" | "PT" | "PW" | "PY" | "QA" | "RE" | "RO" | "RS" | "RU" | "RW" | "SA" | "SB" | "SC" | "SD" | "SE" | "SG" | "SH" | "SI" | "SJ" | "SK" | "SL" | "SM" | "SN" | "SO" | "SR" | "SS" | "ST" | "SV" | "SX" | "SY" | "SZ" | "TA" | "TC" | "TD" | "TG" | "TH" | "TJ" | "TK" | "TL" | "TM" | "TN" | "TO" | "TR" | "TT" | "TV" | "TW" | "TZ" | "UA" | "UG" | "US" | "UY" | "UZ" | "VA" | "VC" | "VE" | "VG" | "VI" | "VN" | "VU" | "WF" | "WS" | "XK" | "YE" | "YT" | "ZA" | "ZM" | "ZW"FIISO 3166-1 alpha-2 code used when the value has no country prefix.No
size"sm" | "md" | "lg"mdField size, matching TextInput; lg is the 3rem height every lg field shares.No
onChange(value: string | undefined) => voidnoneNo description in the contract.No

Variants

  • Available sizes: sm, md, lg (default md). Forwarded to CSS module size classes.
  • Allowed defaultCountry values: AC, AD, AE, AF, AG, AI, AL, AM, AO, AR, AS, AT, AU, AW, AX, AZ, BA, BB, BD, BE, BF, BG, BH, BI, BJ, BL, BM, BN, BO, BQ, BR, BS, BT, BW, BY, BZ, CA, CC, CD, CF, CG, CH, CI, CK, CL, CM, CN, CO, CR, CU, CV, CW, CX, CY, CZ, DE, DJ, DK, DM, DO, DZ, EC, EE, EG, EH, ER, ES, ET, FI, FJ, FK, FM, FO, FR, GA, GB, GD, GE, GF, GG, GH, GI, GL, GM, GN, GP, GQ, GR, GT, GU, GW, GY, HK, HN, HR, HT, HU, ID, IE, IL, IM, IN, IO, IQ, IR, IS, IT, JE, JM, JO, JP, KE, KG, KH, KI, KM, KN, KP, KR, KW, KY, KZ, LA, LB, LC, LI, LK, LR, LS, LT, LU, LV, LY, MA, MC, MD, ME, MF, MG, MH, MK, ML, MM, MN, MO, MP, MQ, MR, MS, MT, MU, MV, MW, MX, MY, MZ, NA, NC, NE, NF, NG, NI, NL, NO, NP, NR, NU, NZ, OM, PA, PE, PF, PG, PH, PK, PL, PM, PR, PS, PT, PW, PY, QA, RE, RO, RS, RU, RW, SA, SB, SC, SD, SE, SG, SH, SI, SJ, SK, SL, SM, SN, SO, SR, SS, ST, SV, SX, SY, SZ, TA, TC, TD, TG, TH, TJ, TK, TL, TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, US, UY, UZ, VA, VC, VE, VG, VI, VN, VU, WF, WS, XK, YE, YT, ZA, ZM, ZW (default FI).

Invalid combinations

The contract records no invalid combinations.

Keyboard

PhoneInput keyboard behaviour
KeyBehaviour
TabMoves focus to the control and on to the next focusable element. (standard)
Arrow keys(country select)
Home / End(country select, while the list is open)
Enter, Space, EscapeAnd typeahead by name or calling code (country select)
GeneralTyping digits.

Rows marked (standard) name a key the contract lists without a description; the behaviour shown is the usual one for that key.

Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

PhoneInput rules
LevelRuleHow it is checked
MUSTARIA: Label associated via htmlFor/id.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Error via HelperText linked with aria-describedby + aria-invalid.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Country picker is a select-only combobox (aria-expanded, aria-controls, aria-activedescendant) named from the library's localized label only; the visible "FI +358" text is not part of the accessible name.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTRequired stories produce no axe violations at the configured severity.Automated by npm run test:stories:matrix:ci. WCAG 2.5.8.
MUSTThe accessibility tree (role, name, state) matches the committed snapshot for every required story.Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTEvery documented keyboard interaction works as specified.Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-11-04.
SHOULDValidate with isValidPhoneNumber from react-phone-number-input on submit; the field formats but does not block invalid numbers.Not stated in the contract.
SHOULDStore and send the emitted E.164 value verbatim; it is the interchange format your backend and SMS providers expect.Not stated in the contract.
SHOULDSet defaultCountry to the audience you actually serve; users typing a local number get the right prefix for free.Not stated in the contract.
SHOULDTreat onChange emitting undefined as "incomplete", not as an error; only validate on blur or submit.Not stated in the contract.
SHOULD NOTDo not pre-split country code and number into two fields; the combined control is the accessible, error-proof pattern.Not stated in the contract.

Install and import

  • Install: npm install @digitaltableteur/react (version 0.9.1, restricted access on npm).
  • Import: import { PhoneInput } from "@digitaltableteur/react";
  • Entry point: @digitaltableteur/react/forms exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.
  • Also available as the custom element <dt-phone-input>; see the custom elements manifest.