Eunomia v1.0.0-beta.2
AllAngularReactNext.jsJavaScriptVue
EunomiaSalmonForestVioletOceanGoldFireCustom…
🇬🇧 English🇫🇷 Français

Input phone

Input phone is a field for collecting an international phone number, rendered as two adjacent parts: a compact, flag-only country selector on the leading side that opens a searchable dropdown, and the phone field itself on the trailing side. Typed digits are grouped for display as you type, and the country dropdown opens using the same positioning behavior as Select's own dropdown.

No phone-number library is used. The country and dial-code list is a small, hand-maintained table, and the digits shown while typing are grouped with a generic, non-authoritative heuristic, not real per-country formatting. Typing is capped at a generous upper bound sourced from public numbering plans, not a validated length, so this is not true validation. If strict validation or locale-accurate formatting matters, validate the emitted value server-side, or with a real phone-number library, rather than relying on this component alone.

Dependencies

eun-label · if it has a label
Overview API Examples Accessibility

When to use

Use input phone to collect an international phone number where the person filling the form may need to pick a country other than your application's default. For a single, known country with no need for a country selector, a plain input set up for phone numbers is simpler and lighter. See Alternatives below for a side by side comparison.

Install & usage

Pick a framework in the toolbar above and these snippets adapt.

npm install @eunomia/elements
import "@eunomia/elements/input-phone.js";
import "@eunomia/elements/label.js";
<eun-input-phone label="Phone number" country="FR"></eun-input-phone>
document
  .querySelector("eun-input-phone")
  .addEventListener("eunchange", (event) => {
    console.log(event.target.value); // e.g. "+33612345678"
  });

Importing the file registers <eun-input-phone> as a custom element, with no further setup needed. It works with any framework, or none, since it's a standard web component.

Alternatives

You want .. Prefers A single, known country with no need for a country selector, simpler and lighter eun-input

Guidance

  • Set country or default-country to whatever your application already knows about the person (billing address, browser locale), so the selector starts on the right flag instead of the generic globe
  • Treat the emitted value (E.164) as unvalidated beyond a basic length cap: pair it with a server-side check (or a real phone-number library) wherever correctness genuinely matters, e.g. before sending an SMS
  • Restrict the dropdown with slotted <option> elements when only a handful of countries are ever realistically valid for your form (e.g. a regional service), so the person isn't scrolling through ~195 countries to find one of three
  • See Alternatives above when there's no need for a country selector at all
  • Relying on the grouped digits shown in the field, or on value's length cap, as real per-country validation: neither is phone-number-library-accurate (see the warning above)
  • Setting hide-country-selector without also setting country (or default-country): with no picker UI and no country, value can never be built (there's no dial code to build it with), and typed digits go nowhere

Live testing

Properties

InputPhone <eun-input-phone>

Attributes

NameTypeDefaultDescription
default-countrystringThe country applied when the field connects, when country isn't already set
hide-country-selectorbooleanfalseHides the leading country selector part entirely. Country can still be set programmatically with no picker shown
hide-dial-codebooleanfalseHides the dial code prefix inside the phone field
country-search-placeholderstring'Search a country…'The placeholder of the search field inside the country dropdown
no-results-labelstring'No results'Text displayed in the dropdown when no country matches the search
aria-label-country-selectorstring'Select a country'The accessible label of the leading country selector button
countrystringThe selected country, as a two letter country code. No default is forced, so value stays empty until a country is selected. The country selector button shows a generic globe glyph while unset
placeholderstringThe placeholder shown when the field is empty. Defaults to a generic formatted digit pattern, not a real per-country example, when unset
localestring | undefinedThe locale used to display each country's name in the dropdown (via `Intl.DisplayNames`) ; defaults to English.

Import the exact TypeScript type behind any property above, see Types.

Properties

JS-only — no matching HTML attribute, set these from a script or a template binding.

NameTypeDefaultDescription
variant'outline' | 'fill' | 'underline''outline'The visual variant to apply to the field
labelstringThe title label of the field, rendered through the label component
instructionsstringInstructions displayed below the label
namestringThe field name
valuestringThe field value, as an E.164 string, such as +33612345678
defaultValuestringThe default value applied when the field connects, and restored on form reset, as an E.164 string
disabledbooleanfalseWhether the field is disabled
readonlybooleanfalseWhether the field is read only
requiredbooleanfalseWhether the field is required
hideErrorbooleanfalseWhether the errors are hidden
validatorsArray<Validators<string>>The list of validation rules applied to the field value

Slots

NameDescription
(default)An optional list of option elements restricting which countries appear in the dropdown, such as an option with value FR for France. When omitted, every built-in country is available

Events

NameTypeDescription
eunchangeChangeEventFired on every value change

Every event above follows the same naming convention, covered in Events.

CSS custom properties

NameDescription
--field-backgroundSets the background color of the field
--field-border-colorSets the border color of the field
--field-border-color-hoverSets the border color on hover
--field-border-color-focusSets the border color on focus
--field-border-radiusSets the corner radius of the field
--field-paddingSets the padding of the field
--field-text-colorSets the text color of the field
--field-placeholder-colorSets the color of the placeholder
--field-icon-colorSets the color of the country selector button
--field-error-colorSets the color of the error message
--field-hint-colorSets the color of the hint message
--field-focus-outline-colorSets the color of the focus outline
--field-listbox-max-heightSets the maximum height of the country dropdown panel, past which it scrolls
--field-listbox-transition-durationSets the duration of the dropdown panel's open and close height transition, disabled entirely under reduced motion settings
--eun-viewport-inset-topReserves space, such as a page's sticky header height, that the dropdown panel keeps clear of at the top of the viewport. Set on the root or an ancestor rather than on the field itself
--eun-viewport-inset-bottomThe same as eun-viewport-inset-top, reserved at the bottom of the viewport, such as for a sticky footer

Countries

Every country the dropdown offers by default (i.e. with no restricting <option> children, see "Country dropdown" in the Examples tab), with the flag/name shown in its own row and the +<dial code> prefix plus maximum national number length (221 total) this component derives from it. Generated straight from input-phone.type.ts's own table, so it always matches what actually ships.

Flag Country ISO Dial code Max digits
🇦🇫 Afghanistan AF +93 9
🇦🇱 Albania AL +355 9
🇩🇿 Algeria DZ +213 9
🇦🇸 American Samoa AS +1684 7
🇦🇩 Andorra AD +376 6
🇦🇴 Angola AO +244 9
🇦🇮 Anguilla AI +1264 7
🇦🇬 Antigua & Barbuda AG +1268 7
🇦🇷 Argentina AR +54 10
🇦🇲 Armenia AM +374 8
🇦🇼 Aruba AW +297 7
🇦🇺 Australia AU +61 9
🇦🇹 Austria AT +43 11
🇦🇿 Azerbaijan AZ +994 9
🇧🇸 Bahamas BS +1242 7
🇧🇭 Bahrain BH +973 8
🇧🇩 Bangladesh BD +880 10
🇧🇧 Barbados BB +1246 7
🇧🇾 Belarus BY +375 9
🇧🇪 Belgium BE +32 9
🇧🇿 Belize BZ +501 7
🇧🇯 Benin BJ +229 8
🇧🇹 Bhutan BT +975 8
🇧🇴 Bolivia BO +591 8
🇧🇦 Bosnia & Herzegovina BA +387 8
🇧🇼 Botswana BW +267 8
🇧🇷 Brazil BR +55 11
🇻🇬 British Virgin Islands VG +1284 7
🇧🇳 Brunei BN +673 7
🇧🇬 Bulgaria BG +359 9
🇧🇫 Burkina Faso BF +226 8
🇧🇮 Burundi BI +257 8
🇰🇭 Cambodia KH +855 9
🇨🇲 Cameroon CM +237 9
🇨🇦 Canada CA +1 10
🇨🇻 Cape Verde CV +238 7
🇰🇾 Cayman Islands KY +1345 7
🇨🇫 Central African Republic CF +236 8
🇹🇩 Chad TD +235 8
🇨🇱 Chile CL +56 9
🇨🇳 China CN +86 11
🇨🇴 Colombia CO +57 10
🇰🇲 Comoros KM +269 7
🇨🇬 Congo - Brazzaville CG +242 9
🇨🇩 Congo - Kinshasa CD +243 9
🇨🇰 Cook Islands CK +682 5
🇨🇷 Costa Rica CR +506 8
🇨🇮 Côte d’Ivoire CI +225 10
🇭🇷 Croatia HR +385 9
🇨🇺 Cuba CU +53 8
🇨🇾 Cyprus CY +357 8
🇨🇿 Czechia CZ +420 9
🇩🇰 Denmark DK +45 8
🇩🇯 Djibouti DJ +253 8
🇩🇲 Dominica DM +1767 7
🇩🇴 Dominican Republic DO +1809 7
🇪🇨 Ecuador EC +593 9
🇪🇬 Egypt EG +20 10
🇸🇻 El Salvador SV +503 8
🇬🇶 Equatorial Guinea GQ +240 9
🇪🇷 Eritrea ER +291 7
🇪🇪 Estonia EE +372 8
🇸🇿 Eswatini SZ +268 8
🇪🇹 Ethiopia ET +251 9
🇫🇴 Faroe Islands FO +298 6
🇫🇯 Fiji FJ +679 7
🇫🇮 Finland FI +358 10
🇫🇷 France FR +33 9
🇬🇫 French Guiana GF +594 9
🇵🇫 French Polynesia PF +689 6
🇬🇦 Gabon GA +241 8
🇬🇲 Gambia GM +220 7
🇬🇪 Georgia GE +995 9
🇩🇪 Germany DE +49 11
🇬🇭 Ghana GH +233 9
🇬🇮 Gibraltar GI +350 8
🇬🇷 Greece GR +30 10
🇬🇱 Greenland GL +299 6
🇬🇩 Grenada GD +1473 7
🇬🇵 Guadeloupe GP +590 9
🇬🇺 Guam GU +1671 7
🇬🇹 Guatemala GT +502 8
🇬🇳 Guinea GN +224 9
🇬🇼 Guinea-Bissau GW +245 7
🇬🇾 Guyana GY +592 7
🇭🇹 Haiti HT +509 8
🇭🇳 Honduras HN +504 8
🇭🇰 Hong Kong SAR China HK +852 8
🇭🇺 Hungary HU +36 9
🇮🇸 Iceland IS +354 7
🇮🇳 India IN +91 10
🇮🇩 Indonesia ID +62 11
🇮🇷 Iran IR +98 10
🇮🇶 Iraq IQ +964 10
🇮🇪 Ireland IE +353 9
🇮🇱 Israel IL +972 9
🇮🇹 Italy IT +39 10
🇯🇲 Jamaica JM +1876 7
🇯🇵 Japan JP +81 10
🇯🇴 Jordan JO +962 9
🇰🇿 Kazakhstan KZ +7 10
🇰🇪 Kenya KE +254 9
🇰🇮 Kiribati KI +686 5
🇰🇼 Kuwait KW +965 8
🇰🇬 Kyrgyzstan KG +996 9
🇱🇦 Laos LA +856 9
🇱🇻 Latvia LV +371 8
🇱🇧 Lebanon LB +961 8
🇱🇸 Lesotho LS +266 8
🇱🇷 Liberia LR +231 8
🇱🇾 Libya LY +218 9
🇱🇮 Liechtenstein LI +423 7
🇱🇹 Lithuania LT +370 8
🇱🇺 Luxembourg LU +352 9
🇲🇴 Macao SAR China MO +853 8
🇲🇬 Madagascar MG +261 9
🇲🇼 Malawi MW +265 9
🇲🇾 Malaysia MY +60 9
🇲🇻 Maldives MV +960 7
🇲🇱 Mali ML +223 8
🇲🇹 Malta MT +356 8
🇲🇭 Marshall Islands MH +692 7
🇲🇶 Martinique MQ +596 9
🇲🇷 Mauritania MR +222 8
🇲🇺 Mauritius MU +230 8
🇾🇹 Mayotte YT +262 9
🇲🇽 Mexico MX +52 10
🇫🇲 Micronesia FM +691 7
🇲🇩 Moldova MD +373 8
🇲🇨 Monaco MC +377 8
🇲🇳 Mongolia MN +976 8
🇲🇪 Montenegro ME +382 8
🇲🇦 Morocco MA +212 9
🇲🇿 Mozambique MZ +258 9
🇲🇲 Myanmar (Burma) MM +95 9
🇳🇦 Namibia NA +264 9
🇳🇷 Nauru NR +674 7
🇳🇵 Nepal NP +977 10
🇳🇱 Netherlands NL +31 9
🇳🇨 New Caledonia NC +687 6
🇳🇿 New Zealand NZ +64 9
🇳🇮 Nicaragua NI +505 8
🇳🇪 Niger NE +227 8
🇳🇬 Nigeria NG +234 10
🇳🇺 Niue NU +683 4
🇰🇵 North Korea KP +850 10
🇲🇰 North Macedonia MK +389 8
🇳🇴 Norway NO +47 8
🇴🇲 Oman OM +968 8
🇵🇰 Pakistan PK +92 10
🇵🇼 Palau PW +680 7
🇵🇸 Palestinian Territories PS +970 9
🇵🇦 Panama PA +507 8
🇵🇬 Papua New Guinea PG +675 8
🇵🇾 Paraguay PY +595 9
🇵🇪 Peru PE +51 9
🇵🇭 Philippines PH +63 10
🇵🇱 Poland PL +48 9
🇵🇹 Portugal PT +351 9
🇵🇷 Puerto Rico PR +1787 7
🇶🇦 Qatar QA +974 8
🇷🇪 Réunion RE +262 9
🇷🇴 Romania RO +40 9
🇷🇺 Russia RU +7 10
🇷🇼 Rwanda RW +250 9
🇼🇸 Samoa WS +685 7
🇸🇲 San Marino SM +378 10
🇸🇹 São Tomé & Príncipe ST +239 7
🇸🇦 Saudi Arabia SA +966 9
🇸🇳 Senegal SN +221 9
🇷🇸 Serbia RS +381 9
🇸🇨 Seychelles SC +248 7
🇸🇱 Sierra Leone SL +232 8
🇸🇬 Singapore SG +65 8
🇸🇰 Slovakia SK +421 9
🇸🇮 Slovenia SI +386 8
🇸🇧 Solomon Islands SB +677 7
🇸🇴 Somalia SO +252 8
🇿🇦 South Africa ZA +27 9
🇰🇷 South Korea KR +82 10
🇸🇸 South Sudan SS +211 9
🇪🇸 Spain ES +34 9
🇱🇰 Sri Lanka LK +94 9
🇰🇳 St. Kitts & Nevis KN +1869 7
🇱🇨 St. Lucia LC +1758 7
🇵🇲 St. Pierre & Miquelon PM +508 6
🇻🇨 St. Vincent & Grenadines VC +1784 7
🇸🇩 Sudan SD +249 9
🇸🇷 Suriname SR +597 7
🇸🇪 Sweden SE +46 9
🇨🇭 Switzerland CH +41 9
🇸🇾 Syria SY +963 9
🇹🇼 Taiwan TW +886 9
🇹🇯 Tajikistan TJ +992 9
🇹🇿 Tanzania TZ +255 9
🇹🇭 Thailand TH +66 9
🇹🇱 Timor-Leste TL +670 8
🇹🇬 Togo TG +228 8
🇹🇴 Tonga TO +676 7
🇹🇹 Trinidad & Tobago TT +1868 7
🇹🇳 Tunisia TN +216 8
🇹🇷 Türkiye TR +90 10
🇹🇲 Turkmenistan TM +993 8
🇹🇨 Turks & Caicos Islands TC +1649 7
🇹🇻 Tuvalu TV +688 6
🇻🇮 U.S. Virgin Islands VI +1340 7
🇺🇬 Uganda UG +256 9
🇺🇦 Ukraine UA +380 9
🇦🇪 United Arab Emirates AE +971 9
🇬🇧 United Kingdom GB +44 10
🇺🇸 United States US +1 10
🇺🇾 Uruguay UY +598 8
🇺🇿 Uzbekistan UZ +998 9
🇻🇺 Vanuatu VU +678 7
🇻🇦 Vatican City VA +379 10
🇻🇪 Venezuela VE +58 10
🇻🇳 Vietnam VN +84 10
🇼🇫 Wallis & Futuna WF +681 6
🇾🇪 Yemen YE +967 9
🇿🇲 Zambia ZM +260 9
🇿🇼 Zimbabwe ZW +263 9

Variants

Set variant to switch between the bordered box (outline, default), a filled background with no border (fill), and a bottom-only border (underline).

<eun-input-phone
  variant="fill"
  label="Phone number"
  country="FR"
></eun-input-phone>
<eun-input-phone
  variant="underline"
  label="Phone number"
  country="FR"
></eun-input-phone>

Part of the shared vocabulary covered in Variants, alongside every other component that reuses it.

Value format

The value emitted through eunchange (and used for form submission) is an E.164 string, e.g. +33612345678: the country's dial code followed by the raw digits, with no spaces. The visible field only ever shows the grouped national digits.

<eun-input-phone
  label="Phone number"
  country="FR"
  value="+33612345678"
></eun-input-phone>

Domestic format (leading 0)

Most numbering plans in this table write their numbers with a leading trunk 0 domestically (e.g. a French mobile is dialed 06 12 34 56 78 at home, but +33 6 12 34 56 78 abroad). Type it that way and the leading 0 stays visible in the field exactly as typed: it's only excluded from value (E.164 has no place for it) and from the country's own digit cap, so it never displaces a real digit off the end to make room for a 0 that was never part of the significant number to begin with. The default placeholder reflects the same convention, shown as (0) ahead of the digits, the parentheses being the common way to signal "domestic-only, drop it internationally".

<eun-input-phone label="Phone number" country="FR"></eun-input-phone>
Typed:     0612345678    (10 digits, domestic format)
Displayed: 06 12 34 56 78 (leading 0 kept)
value:     +33612345678  (leading 0 excluded)

Per-country digit limit

Typing stops accepting new digits once the selected country's own maximum national number length is reached: 9 for France below, so an 11-digit paste only keeps the first 9. Switching country re-trims whatever's already typed to the newly-selected country's own limit, in case it's shorter (try pasting the number below, then switching to Andorra in the dropdown).

<eun-input-phone label="Phone number" country="FR"></eun-input-phone>
Typed: 61234567890 (11 digits)
Kept:  612345678   (9 digits, France's own maximum)

No country selected

country has no forced default: until one is set (by you, default-country, or through the dropdown), the field still accepts typed digits, but value stays empty since there's no dial code to build an E.164 string with. The country-selector button shows a generic globe glyph (🌐) instead of a blank flag while unset.

<eun-input-phone label="Phone number"></eun-input-phone>

Placeholder

When placeholder isn't set explicitly, the field falls back to a formatted generic digit pattern prefixed with (0), the conventional parenthesized trunk prefix that hints a leading 0 is welcome and domestic-only. It always shows the grouping style typed digits will get, though this is not a real per-country example number (no phone-number library is used, so there's no per-country length data to draw one from). Set placeholder explicitly to override it.

<eun-input-phone
  label="Phone number"
  country="FR"
  placeholder="Your phone number"
></eun-input-phone>

Hiding the dial code indicator

Set hide-dial-code to omit the +<dial code> prefix inside the phone field. It's useful in tight layouts, or when the dial code is shown elsewhere.

<eun-input-phone
  hide-dial-code
  label="Phone number"
  country="FR"
></eun-input-phone>

Hiding the country selector

Set hide-country-selector to remove the leading flag/dropdown part entirely, leaving only the phone field. country can still be set programmatically (e.g. a fixed-country form): there's simply no picker UI to change it.

<eun-input-phone
  hide-country-selector
  label="Phone number"
  country="FR"
></eun-input-phone>

Country dropdown

The leading button opens a searchable list of countries (flag, name, dial code), filtered as you type in its own search field. Like eun-select's own dropdown, it's a popover="auto" element: rendered in the browser's top layer and dismissed automatically on outside click or Escape.

By default, every built-in country is available. Pass <option value="ISO">Name</option> elements as children to restrict the dropdown to a specific set, the same convention eun-select uses for its own choices. default-country sets the initial selection when country isn't provided explicitly.

<eun-input-phone label="Phone number" default-country="FR">
  <option value="FR">France</option>
  <option value="BE">Belgium</option>
  <option value="CH">Switzerland</option>
  <option value="CA">Canada</option>
  <option value="US">United States</option>
  <option value="GB">United Kingdom</option>
  <option value="DE">Germany</option>
</eun-input-phone>

States

Disabled

<eun-input-phone
  disabled
  label="Phone number"
  country="FR"
  value="+33612345678"
></eun-input-phone>

Readonly

<eun-input-phone
  readonly
  label="Phone number"
  country="FR"
  value="+33612345678"
></eun-input-phone>

Required

<eun-input-phone required label="Phone number" country="FR"></eun-input-phone>

Hint and validation

Same hint/validators/hide-error mechanics as every other field built on FormMixin. See Forms & validation for the full pattern.

<eun-input-phone
  label="Phone number"
  country="FR"
  hint="We'll only text you a verification code."
></eun-input-phone>

Keyboard interactions

Key Action
Tab / Shift+Tab Moves focus between the country-selector button, the phone field, and the rest of the page
Enter / Space, on the country-selector button Opens the country dropdown (native <button> behavior)
ArrowDown / ArrowUp, inside the dropdown's search field Moves the highlight to the next/previous matching country
Enter, inside the dropdown's search field Selects the highlighted country, closes the dropdown, and returns focus to the phone field
Any character key, inside the dropdown's search field Filters the country list live
Escape Closes the dropdown (native Popover API light-dismiss)

Dismissal behavior

The dropdown closes on: Escape, a click outside it (both native Popover API light-dismiss), picking a country, the browser window itself losing focus (e.g. Alt-Tabbing to another application, not something the Popover API handles on its own, since light-dismiss only reacts to clicks and Escape), and focus leaving the trigger/dropdown entirely (e.g. Tab-ing past the search field, since light-dismiss doesn't cover keyboard-only focus changes either). It also closes if the field itself scrolls behind a reserved --eun-viewport-inset-top/-bottom band while open. See "Dropdown positioning" below, and eun-select's own Accessibility tab, which documents the identical mechanism in more depth.

Aria attributes

  • The country-selector button carries aria-haspopup="listbox", aria-expanded (reflecting whether the dropdown is open), aria-controls="listbox", and a configurable aria-label (ariaLabelCountrySelector, "Select a country" by default): its visible content (a flag only) isn't a sufficient accessible name on its own
  • The dropdown panel itself carries role="listbox" with a matching aria-label, and each country row carries role="option" / aria-selected="true"\|"false"
  • The phone field carries aria-invalid="true"\|"false", kept in sync with the field's validity, and aria-describedby="description" whenever a hint or error message is actually rendered below it
  • The phone field is associated with its eun-label through the for/id relationship, exactly like eun-input. Refer to its Accessibility tab for mandatory-field indication and message management, which apply identically here

Dropdown positioning

The dropdown panel's position tracks the whole field (both the country-selector button and the phone field together) on every scroll and resize, flipping above it instead of below whenever there isn't enough room underneath. This uses the exact same AnchorPositionMixin as eun-select: CSS Anchor Positioning natively where supported, a JS fallback everywhere else, so --eun-viewport-inset-top/--eun-viewport-inset-bottom (reserved space for page chrome like a sticky header) and --field-listbox-max-height both apply here identically. See eun-select's own Accessibility tab ("Dropdown panel: rendering, positioning, and sizing") for the full explanation.

Disabled vs. readonly

disabled removes both the country-selector button and the phone field from the tab order, and excludes the value from form submission entirely. readonly keeps the phone field focusable and its value submitted, but prevents typing into it and disables the country-selector button, since there's nothing to change. This communicates "you can't change this right now", not "this doesn't apply".

Label accessibility

Always set label (or slot content into eun-label's label slot): a phone field with no accessible name is as disruptive for screen-reader users as any other unlabeled text field. The country-selector button's own aria-label (ariaLabelCountrySelector) is separate from this and always present, since a flag glyph alone never makes an adequate accessible name.

Reference links

WAI-ARIA Authoring Practices: Combobox Pattern
WAI-ARIA Authoring Practices: Listbox Pattern
WAI Web Accessibility Tutorials: Labeling Controls
WHATWG HTML: Popover API
CSS Anchor Positioning: Working Draft