- Accordion
- Action Bar
- Action Toast
- Alert Banner
- Alert Dialog
- Alert
- Alphabet Index
- Amount Field
- Aspect Ratio
- Attachment
- Availability List
- Avatar
- Badge
- Bank Card
- Breadcrumb
- Bubble
- Button Group
- Button
- Calendar
- Camera Frame
- Card Design Picker
- Card Details
- Card Peek
- Card Stack
- Card
- Carousel
- Chart
- Chat Thread
- Checkbox
- Chip Group
- Choice Tile
- Code Block
- Code Dots
- Collapsible
- Combobox
- Command
- Context Menu
- Copy Field
- Countdown
- Country Select
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Direction
- Document Paper
- Drawer
- Dropdown Menu
- Empty
- Entity Avatar
- Field
- File Trigger
- File Upload Item
- Hover Card
- Input Group
- Input OTP
- Input
- Item
- Kbd
- Key Value
- Keypad
- Label
- Live Region
- Marker
- Message Scroller
- Message
- Money Amount
- Native Select
- Number Field
- Pagination
- Percent Split Editor
- Phone Field
- Popover
- Progress
- QR Code
- Questionnaire
- Radio Group
- Range Histogram
- Rating
- Resizable
- Scroll Area
- Search Field
- Select
- Separator
- Share Bar
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Sortable List
- Spinner
- Status Timeline
- Step Header
- Steps List
- Swipe Row
- Switch
- Table
- Tabs
- Text Field
- Textarea
- Tier Ladder
- Toggle Group
- Toggle
- Token Search Field
- Tooltip
- Trace
- Transaction Row
- Usage Meter
"use client"
import type { Country } from "@/components/ui/country-select"The PhoneField component takes a phone number with its country: a calling code button that opens a searchable country list, and the national number formatted by the country mask as the user types. Use it for sign-up, two-step verification and paying a contact by phone. Pick it over TextField with type="tel" whenever the number may be international, since the country picker, the mask and paste handling come built in.
Installation#
pnpm dlx shadcn@latest add @dots/phone-field
Usage#
import type { Country } from "@/components/ui/country-select"
import { PhoneField } from "@/components/ui/phone-field"const countries: Country[] = [
{ code: "GB", name: "United Kingdom", dialCode: "+44", phoneMask: "#### ######" },
{ code: "US", name: "United States", dialCode: "+1", phoneMask: "(###) ###-####" },
]
<PhoneField label="Phone number" countries={countries} defaultCountry="US" />Features#
- A calling code button that opens the searchable
CountrySelectlist, with an optional popular group - The national number is formatted by the country's
phoneMaskas the user types, one#per digit - Typing or pasting a number that starts with a plus, such as
+44 7700 900123, switches the country and keeps the national digits - A check at the end once the number fills the mask
valueandonChangework with digits only, so you never parse the formatted string- With
name, forms receive the full number with its calling code, such as+447700900123 - The country button and the number share one invalid and disabled state
Countries#
Each country needs a dialCode. Add a phoneMask to format the number and cap its length; without one the field takes up to 15 digits unformatted. The placeholder defaults to the mask with zeros. Pass the codes you expect most to popular to list them first.
const countries: Country[] = [
{ code: "DE", name: "Germany", dialCode: "+49", phoneMask: "### ########" },
{ code: "AE", name: "United Arab Emirates", dialCode: "+971", phoneMask: "## ### ####" },
{ code: "US", name: "United States", dialCode: "+1", phoneMask: "(###) ###-####" },
]When two countries share a calling code, such as +1, the selected country wins if it is one of them. Otherwise the first country listed with that code is picked, so order countries with the most likely one first.
Complete#
Once the national number fills the mask, the field shows a check at the end and gets data-complete. Set showComplete={false} to hide the check.
"use client"
import type { Country } from "@/components/ui/country-select"Controlled#
Control the country with country and onCountryChange, and the number with value and onChange. Both take plain values: a country code and the national digits. Try pasting +44 7700 900123 to see the country switch.
+1
"use client"
import * as React from "react"Invalid#
Set isInvalid and pass an errorMessage that says how to fix the number. The country button and the number both take the invalid fill.
"use client"
import type { Country } from "@/components/ui/country-select"Disabled#
Use isDisabled for a number that can't be changed here, such as a verified one. Say why in the description.
"use client"
import type { Country } from "@/components/ui/country-select"Accessibility#
PhoneField is built on the React Aria TextField, so the label, description and error message are wired to the number input, and invalid and disabled states are announced. The country picker is a React Aria Select with a searchable list.
Label the field#
Pass a visible label. Without one, pass aria-label so the number input has a name.
<PhoneField aria-label="Phone number" countries={countries} />Name the country button#
The country button shows only a calling code, so it is named by countryLabel, which defaults to "Country code". Translate it with the rest of your interface, along with searchLabel for the search field.
Input mode#
The number input uses type="tel", inputMode="tel" and autoComplete="tel-national", so phones show the dial pad and browsers can fill a saved number.
API Reference#
PhoneField#
Accepts the React Aria TextField props except value, defaultValue, onChange, type, inputMode, children and className, which are replaced below.
| Prop | Type | Default | Description |
|---|---|---|---|
countries | Country[] | - | Countries to pick the calling code from. Each needs dialCode; phoneMask formats the number. |
popular | string[] | - | Country codes listed first. |
country | string | - | The selected country code (controlled), such as "GB". |
defaultCountry | string | First available country | The selected country code (uncontrolled). |
onCountryChange | (code: string) => void | - | Called when the country changes, by picking or by typing a plus code. |
value | string | - | The national number as digits only (controlled), such as "7700900123". |
defaultValue | string | "" | The national number as digits only (uncontrolled). |
onChange | (digits: string) => void | - | Called with the national digits. |
label | ReactNode | - | The visible label. Without it, pass aria-label. |
description | ReactNode | - | Help text under the field. |
errorMessage | ReactNode | (validation: ValidationResult) => ReactNode | - | Shown when the field is invalid. |
placeholder | string | The mask with zeros | Placeholder for the number, such as "0000 000000". |
showComplete | boolean | true | Show a check at the end once the number fills the mask. |
countryLabel | string | "Country code" | The accessible name of the country button. |
searchLabel | string | "Search country or code" | The label of the search field in the country list. |
emptyMessage | ReactNode | - | Shown when the search finds no country. |
name | string | - | Adds a hidden input with the full number and calling code, such as "+447700900123". |
isInvalid | boolean | - | Marks the country button and the number as invalid. |
isDisabled | boolean | - | Disables the country button and the number. |
className | string | - | Additional classes to apply to the root element. |
Country#
The country shape, exported from components/ui/country-select.
| Field | Type | Default | Description |
|---|---|---|---|
code | string | - | ISO 3166-1 alpha-2 code, such as "GB". Used as the key. |
name | string | - | The name in the interface language. |
dialCode | string | - | The calling code with the plus, such as "+44". Searchable. |
phoneMask | string | - | The national number mask, one # per digit. |
flag | ReactNode | - | A monochrome flag. Without it the code is shown in a round chip. |
isUnavailable | boolean | - | Listed but not selectable. |