A polished country calling-code picker for Compose Multiplatform.
Search countries, display recent selections, format input, and validate phone numbers locally on Android and iOS.
Website · Live Demo · Features · Installation · Quick Start · API Reference · Validation · Platform Support
| Bottom sheet | Dialog | Full screen |
|---|---|---|
![]() |
![]() |
![]() |
Add CountryCodeKit Compose to the Compose Multiplatform source set where the picker is used:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.tharukack:countrycodekit:1.1.0")
}
}
}CountryCodeKit Compose 1.1.0 is built and tested with:
| Dependency | Tested version |
|---|---|
| Kotlin | 2.3.0 |
| Compose Multiplatform | 1.11.1 |
| Material 3 | 1.10.0-alpha05 |
Align your application with these versions, or verify dependency compatibility when using an older Compose Multiplatform stack.
The minimal implementation needs only a saveable state and the picker:
val pickerState = rememberCountryCodePickerState()
CountryCodePicker(
state = pickerState,
)This gives you the default US selection, flag/calling-code/chevron trigger, bottom-sheet presentation, searchable complete catalog, calling codes, up to three recent selections, alphabetical sections, bundled flags, and Material typography and text colors. Selecting a country updates pickerState.selectedCountry and closes the picker.
Read the selected value anywhere in your UI:
val selected = pickerState.selectedCountry
Text(selected.name)
Text(selected.formattedCallingCode)To choose the initial country or seed recents when the library has no stored selections yet, provide the country once and use plain ISO codes for recents:
val australia = CountryCodeCatalog.findByIsoCode("AU")!!
val pickerState = rememberCountryCodePickerState(
initialCountry = australia,
initialRecentSelections = listOf("AU", "NZ", "US"),
)The three most recent manual selections are shared automatically between picker instances and persisted locally on the device. They survive navigation, picker-state recreation, process termination, and app restarts. Country detection does not add a recent selection.
This example uses the default bottom-sheet style as a complete phone-field integration. It shows picker customization together with app-owned text input, as-you-type formatting, full validation, automatic country detection, country filtering, recent selections, and normalized outputs. Keep only the values your application needs.
val australia = CountryCodeCatalog.findByIsoCode("AU")!!
// This is the picker configuration chosen by your app.
val pickerConfig = CountryCodePickerConfig(
style = CountryCodePickerStyle.BottomSheet,
countryFilter = CountryCodePickerCountryFilter.Supported(
isoCodes = listOf("AU", "NZ", "US"),
),
bottomSheet = CountryCodePickerBottomSheetConfig(
heightFraction = 0.65f,
gesturesEnabled = false,
shape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp),
showDragHandle = true,
dragHandleWidth = 40.dp,
dragHandleHeight = 4.dp,
dragHandleTopPadding = 10.dp,
dragHandleShape = RoundedCornerShape(100.dp),
borderWidth = 0.dp,
tonalElevation = 0.dp,
minWidth = null,
maxWidth = null,
),
trigger = CountryCodePickerTriggerConfig(
triggerElements = setOf(
CountryCodePickerTriggerElement.Flag,
CountryCodePickerTriggerElement.CountryCode,
CountryCodePickerTriggerElement.Chevron,
),
flagStyle = CountryCodeFlagStyle.Circle,
shape = RoundedCornerShape(14.dp),
borderWidth = 1.dp,
countryCodeTextStyle = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
),
chevronSize = 12.dp,
startPadding = 9.dp,
topPadding = 9.dp,
endPadding = 9.dp,
bottomPadding = 9.dp,
elementSpacing = 6.dp,
colors = CountryCodePickerTriggerColors(
container = Color.Transparent,
content = Color.Unspecified,
chevron = Color.Unspecified,
border = Color(0xFFDDE7E3),
),
),
countryList = CountryCodePickerCountryListConfig(
showSearch = true,
showCallingCode = true,
showRecentSelections = true,
recentSelectionLimit = 3,
separateCountriesByLetter = true,
autoFocusSearch = false,
flagStyle = CountryCodeFlagStyle.Circle,
search = CountryCodePickerSearchConfig(
shape = RoundedCornerShape(14.dp),
height = 48.dp,
horizontalPadding = 12.dp,
iconSize = 19.dp,
iconSpacing = 8.dp,
clearButtonSize = 32.dp,
clearIconSize = 18.dp,
),
selection = CountryCodePickerSelectionConfig(
rowShape = RoundedCornerShape(10.dp),
rowBorderWidth = 1.dp,
rowHorizontalInset = 8.dp,
rowVerticalInset = 2.dp,
rowContentStartPadding = 12.dp,
rowContentEndPadding = 8.dp,
indicatorSize = 20.dp,
recentCardShape = RoundedCornerShape(10.dp),
recentCardBorderWidth = 0.5.dp,
recentCardSpacing = 6.dp,
recentContentPadding = 8.dp,
recentIndicatorHeight = 3.dp,
),
strings = CountryCodePickerStrings(
title = "Select your country",
searchPlaceholder = "Search country or code",
recent = "Recent",
allCountries = "All countries",
searchResults = "Search results",
noResults = "No countries found",
close = "Close",
clearSearch = "Clear search",
),
textStyles = CountryCodePickerCountryListTextStyles(
title = TextStyle(fontWeight = FontWeight.Bold),
search = TextStyle.Default,
sectionTitle = TextStyle(fontWeight = FontWeight.Bold),
letterHeader = TextStyle(fontWeight = FontWeight.Bold),
countryName = TextStyle.Default,
callingCode = TextStyle(fontWeight = FontWeight.SemiBold),
emptyState = TextStyle.Default,
),
colors = CountryCodePickerCountryListColors(
accent = Color(0xFF46BD99),
accentStrong = Color(0xFF2F987A),
sheetContainer = Color.White,
searchContainer = Color(0xFFF4F6F6),
selectedContainer = Color(0xFFEEF8F4),
selectedContent = Color.Unspecified,
content = Color.Unspecified,
secondaryContent = Color.Unspecified,
divider = Color(0xFFE3E9E7),
containerBorder = Color.Transparent,
scrim = Color(0x52000000),
),
),
)
// This optional library state connects the picker with your app-owned phone field.
val phoneState = rememberCountryCodePhoneState(
initialCountry = australia,
initialNumber = "",
initialRecentSelections = listOf("AU", "NZ", "US"),
formatAsYouType = true,
// Validate each edit and detect the country from complete international numbers.
processing = CountryCodePhoneProcessing.ValidateAndDetectCountry,
// Use full country-aware libphonenumber validation.
validationPreset = CountryCodePhoneValidationPreset.PhoneNumber,
pickerConfig = pickerConfig,
)
// This layout and its text field belong to your app.
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(verticalAlignment = Alignment.Top) {
// CountryCodeKit Compose renders the picker and keeps its selected country in phoneState.
CountryCodePicker(
state = phoneState.pickerState,
config = phoneState.pickerConfig,
)
// Your app renders and owns the actual phone-number text field.
OutlinedTextField(
value = phoneState.rawNumber,
onValueChange = phoneState::updateNumber,
modifier = Modifier.weight(1f),
placeholder = { Text("Phone number") },
singleLine = true,
// Formatting is display-only, so raw input and cursor position remain stable.
visualTransformation = phoneState.visualTransformation,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Phone,
),
isError = phoneState.rawNumber.isNotEmpty() && !phoneState.isValid,
supportingText = {
// Validation contains the latest result returned by CountryCodeKit Compose.
phoneState.validation?.let { result ->
Text(
if (result.isValid) "Valid phone number" else result.status.name,
)
}
},
)
}
// Valid numbers expose ready-to-use normalized formats.
phoneState.international?.let { Text("International: $it") }
phoneState.e164?.let { Text("E.164: $it") }
}The OutlinedTextField belongs to the application; CountryCodeKit Compose only supplies state and visualTransformation. updateNumber keeps the stored value unformatted, validates it, and detects the country when a complete valid international number such as +61… is entered. The visualTransformation adds display-only formatting without input delays or cursor jumps. Detection respects pickerConfig.countryFilter, while manual choices become recent selections. Valid results expose both international and e164 values.
Color.Unspecified means “use the host Material theme.” This example uses a supported-country filter; use Unsupported instead when it is shorter to describe the countries your app must hide. Dialog and full-screen settings are intentionally omitted because the selected style is BottomSheet.
The example fully customizes the selected bottom-sheet path and demonstrates the recommended unified phone state. It intentionally leaves out alternatives that replace, disable, or duplicate something already selected:
DialogandFullScreenpresentation configuration. Only the configuration matchingstyleis used; see Picker styles and behavior.- An
Unsupportedcountry filter. It is the mutually exclusive alternative to the demonstratedSupportedfilter; see Restrict countries. - Application-owned flag rendering through
flagContent. It replaces the bundled rounded or circular flags; see Custom flags. - Lighter phone processing modes:
None,Validate, orDetectCountry. The example uses the combinedValidateAndDetectCountrymode. - Other validation presets: possible length, digits only, digits plus possible length, and custom length; see Phone Number Validation.
- Disabling as-you-type formatting, standalone final formatting, or directly using the lower-level validator; see Phone Number Formatting.
- Standard composable controls such as
modifierandenabled, which can be supplied directly toCountryCodePickerwhen required.
This reference explains the public API by responsibility so normal integrations can configure only what they need.
| Area | What you can configure |
|---|---|
| Core picker API | Country data, catalog lookup, picker state, and the picker composable. |
| Picker styles and behavior | Bottom sheet, dialog, full screen, search, recents, sections, and sheet behavior. |
| Trigger | Choose its elements, flag style, shape, spacing, typography, and colors. |
| Country list | Search, calling codes, recents, sections, flags, strings, text, shapes, and colors. |
| Strings | Title, search placeholder, section labels, empty state, and accessibility labels. |
| Country-list colors | Accent, sheet, search, selection, content, divider, and scrim colors. |
| Countries | Provide either supported or unsupported ISO country codes. |
| Flags | Replace bundled artwork with application-owned flag content. |
| Phone formatting | Unified phone state, raw input, visual formatting, country detection, and final formats. |
| Phone validation | Validation presets, result status, and normalized output. |
val pickerState = rememberCountryCodePickerState(
initialCountry = CountryCodeCatalog.findByIsoCode("AU")!!,
initialRecentSelections = listOf("NZ", "US"),
)
CountryCodePicker(
state = pickerState,
modifier = Modifier,
enabled = true,
)| API | Responsibility |
|---|---|
CountryCode |
Immutable selected-country value containing isoCode, name, callingCode, and formattedCallingCode. |
CountryCodeCatalog |
Bundled searchable country catalog with countries, findByIsoCode, search, and hasBundledFlag. |
rememberCountryCodePickerState |
Creates saveable selection, open/closed, and search-query state connected to the library's shared, locally persisted recent selections. Initial recents accept ISO-code strings. |
CountryCodePickerState |
Exposes selectedCountry, isOpen, query, and recentSelections, plus open, dismiss, updateQuery, and select. |
CountryCodePicker |
Renders the trigger and selected presentation using the supplied state and optional configuration. |
CountryCodePickerConfig |
Groups style, filtering, trigger, country-list, and style-specific configuration. |
CountryCodeFlag |
Renders a bundled flag independently when an application needs it outside the picker. |
CountryCodePicker requires only state. Its optional modifier controls placement, config controls behavior and appearance, enabled controls trigger interaction, and flagContent replaces bundled flags in both the trigger and country list.
val config = CountryCodePickerConfig(
style = CountryCodePickerStyle.Dialog,
dialog = CountryCodePickerDialogConfig(
height = 560.dp,
shape = RoundedCornerShape(24.dp),
maxWidth = 520.dp,
dismissOnBackPress = true,
dismissOnClickOutside = true,
),
)
CountryCodePicker(
state = pickerState,
config = config,
)BottomSheet is the default style. Dialog presents the same content in a centered container, and FullScreen uses the available window. Search matches country names, ISO codes, and calling codes. Recent cards appear after manual selections exist or when initial ISO codes seed an empty stored list.
| Option | Default | Purpose |
|---|---|---|
style |
BottomSheet |
Uses a bottom sheet, dialog, or full-screen style. Omit it to use the default. |
countryFilter |
All |
Accepts one supported or unsupported list of string ISO codes. |
trigger |
Default trigger config | Controls trigger elements, shape, border, and colors. |
countryList |
Default country-list config | Controls search, calling codes, recents, sections, strings, and country-list colors. |
bottomSheet |
Default bottom-sheet config | Used only by the BottomSheet style. |
dialog |
Default dialog config | Used only by the Dialog style. |
fullScreen |
Default full-screen config | Used only by the FullScreen style. |
val bottomSheetConfig = CountryCodePickerBottomSheetConfig(
heightFraction = 0.65f,
gesturesEnabled = false,
shape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp),
showDragHandle = true,
maxWidth = 640.dp,
)CountryCodePickerBottomSheetConfig controls height fraction, sheet gestures, top-corner shape, drag-handle visibility/size/padding/shape, outer-border width, tonal elevation, and optional minimum/maximum width. Its defaults preserve the original 0.65f height, disabled gestures, 28 dp top corners, 40 × 4 dp handle, zero border/elevation, and platform-managed width.
val dialogConfig = CountryCodePickerDialogConfig(
height = 560.dp,
shape = RoundedCornerShape(24.dp),
borderWidth = 1.dp,
maxWidth = 520.dp,
dismissOnClickOutside = true,
)CountryCodePickerDialogConfig controls height, shape, outer-border width, tonal and shadow elevation, optional minimum/maximum width, back dismissal, and outside-click dismissal. Its defaults preserve the 620 dp height, 28 dp corners, zero border/elevation, platform-managed width, and standard dismissal behavior.
val fullScreenConfig = CountryCodePickerFullScreenConfig(
contentMaxWidth = 720.dp,
useStatusBarPadding = true,
useNavigationBarPadding = true,
dismissOnBackPress = true,
)CountryCodePickerFullScreenConfig controls optional minimum/maximum content width, status-bar padding, navigation-bar padding, and back dismissal. The full-screen surface itself always fills the window; width limits center the picker content inside it, which is useful on tablets. Defaults preserve full-width content with status-bar padding.
val countryListConfig = CountryCodePickerCountryListConfig(
showSearch = true,
showCallingCode = true,
showRecentSelections = true,
recentSelectionLimit = 3,
separateCountriesByLetter = true,
autoFocusSearch = false,
flagStyle = CountryCodeFlagStyle.Rounded,
)CountryCodePickerCountryListConfig owns everything rendered inside the selected presentation: search, calling-code visibility, recent cards, alphabetical sections, search focus, list flag style, strings, text styles, search shape, selection shape, and colors. The library remembers and displays up to three manual selections across picker instances and app restarts.
Set separateCountriesByLetter = false for one continuous list. showSearch, showCallingCode, and showRecentSelections independently control the corresponding list features. recentSelectionLimit is constrained to the responsive recent-card capacity of three.
val strings = CountryCodePickerStrings(
title = "Choose a country",
searchPlaceholder = "Search name or calling code",
recent = "Recently selected",
allCountries = "Countries",
noResults = "No matching countries",
)CountryCodePickerStrings contains all user-facing and accessibility copy: title, searchPlaceholder, recent, allCountries, searchResults, noResults, close, and clearSearch. Replace these values for localization or product language; none are hard-coded outside this object.
val textStyles = CountryCodePickerCountryListTextStyles(
title = TextStyle(fontSize = 22.sp, fontWeight = FontWeight.Bold),
countryName = TextStyle(fontSize = 15.sp),
callingCode = TextStyle(fontWeight = FontWeight.SemiBold),
)CountryCodePickerCountryListTextStyles provides optional overrides for title, search, sectionTitle, letterHeader, countryName, callingCode, and emptyState. Each override merges with the corresponding host MaterialTheme typography, so unspecified font properties continue to come from the application. Primary and selected text use MaterialTheme.colorScheme.onSurface; secondary text uses onSurfaceVariant unless explicit country-list colors are supplied.
val searchConfig = CountryCodePickerSearchConfig(
shape = RoundedCornerShape(16.dp),
height = 48.dp,
horizontalPadding = 12.dp,
iconSize = 19.dp,
clearButtonSize = 32.dp,
)CountryCodePickerSearchConfig controls the search field without replacing its behavior.
| Option | Default | Purpose |
|---|---|---|
shape |
14 dp rounded | Search container shape. |
height |
48.dp |
Search container height. |
horizontalPadding |
12.dp |
Internal start and end padding. |
iconSize |
19.dp |
Search icon size. |
iconSpacing |
8.dp |
Space between the icon and text. |
clearButtonSize |
32.dp |
Clear-button touch area. |
clearIconSize |
18.dp |
Clear icon size. |
val selectionConfig = CountryCodePickerSelectionConfig(
rowShape = RoundedCornerShape(10.dp),
rowBorderWidth = 1.dp,
rowVerticalInset = 2.dp,
recentCardShape = RoundedCornerShape(10.dp),
recentCardSpacing = 6.dp,
recentIndicatorHeight = 3.dp,
)CountryCodePickerSelectionConfig controls both selected country rows and recent cards. Row options cover shape, border width, horizontal/vertical inset, content padding, and indicator size. Recent-card options cover shape, border width, spacing, content padding, and bottom-indicator height. Their colors remain in CountryCodePickerCountryListColors: selectedContainer, selectedContent, accentStrong, and divider.
val countryListColors = CountryCodePickerCountryListColors(
accent = Color(0xFF46BD99),
accentStrong = Color(0xFF2F987A),
sheetContainer = MaterialTheme.colorScheme.surface,
content = MaterialTheme.colorScheme.onSurface,
secondaryContent = MaterialTheme.colorScheme.onSurfaceVariant,
containerBorder = MaterialTheme.colorScheme.outlineVariant,
)CountryCodeKit Compose ships with a white country-list surface, soft-neutral search field, accessible mint selection colors, and Material-inherited text colors. All country-list colors belong inside CountryCodePickerCountryListConfig.
| Color | Default | Purpose |
|---|---|---|
accent |
#46BD99 |
Primary visual accent. |
accentStrong |
#2F987A |
Calling codes and the selection indicator. |
sheetContainer |
#FFFFFF |
Sheet, dialog, full-screen, and ordinary row background. |
searchContainer |
#F4F6F6 |
Filled search field background. |
selectedContainer |
#EEF8F4 |
Selected-country row background. |
selectedContent |
Material onSurface |
Selected-country name. |
content |
Material onSurface |
Primary text, icons, and letter labels. |
secondaryContent |
Material onSurfaceVariant |
Search icons, placeholders, and empty-state text. |
divider |
#E3E9E7 |
Inset row dividers and the sheet drag handle. |
containerBorder |
Transparent | Outer border used by bottom-sheet and dialog styles when their border width is greater than zero. |
scrim |
32% black | Background dimming behind the sheet. |
val config = CountryCodePickerConfig(
countryFilter = CountryCodePickerCountryFilter.Supported(
isoCodes = listOf("AU", "NZ", "US"),
),
)countryFilter accepts exactly one policy. Use CountryCodePickerCountryFilter.Supported(isoCodes) to show only those countries, Unsupported(isoCodes) to hide those countries, or All for the complete catalog. ISO codes are case-insensitive and unknown values are ignored. The same filter is used by automatic phone-number country detection when the shared phone state receives this picker configuration.
CountryCodePicker(
state = pickerState,
flagContent = { country ->
AppFlag(isoCode = country.isoCode)
},
)Rounded flags are the default. Set flagStyle to Circle independently in the trigger and country-list configurations. Use the composable's flagContent slot to replace bundled PNGs in both locations, or use CountryCodeFlag as a standalone composable. Custom countries without bundled artwork receive a compact ISO fallback inside the flag area; ordinary country rows never show ISO abbreviations.
val triggerConfig = CountryCodePickerTriggerConfig(
triggerElements = setOf(
CountryCodePickerTriggerElement.Flag,
CountryCodePickerTriggerElement.CountryCode,
),
flagStyle = CountryCodeFlagStyle.Circle,
shape = RoundedCornerShape(14.dp),
borderWidth = 1.dp,
countryCodeTextStyle = TextStyle(fontSize = 16.sp),
chevronSize = 12.dp,
elementSpacing = 6.dp,
colors = CountryCodePickerTriggerColors(
container = Color.Transparent,
content = MaterialTheme.colorScheme.onSurface,
border = MaterialTheme.colorScheme.outlineVariant,
),
)CountryCodePickerTriggerConfig controls which elements appear (Flag, CountryCode, and Chevron), flag style, shape, border width, country-code text style, chevron size, four independent padding edges, and spacing between elements. Its colors object controls the container, text, chevron, and border. The container and border are transparent by default. Trigger typography merges with MaterialTheme.typography.bodyMedium; text defaults to onSurface, and the chevron follows the resolved text color unless separately overridden.
val national = CountryCodePhoneFormatter.format(
number = "0412345678",
country = pickerState.selectedCountry,
format = CountryCodePhoneFormat.National,
)
val international = CountryCodePhoneFormatter.format(
number = "0412345678",
country = pickerState.selectedCountry,
format = CountryCodePhoneFormat.International,
)CountryCodeKit Compose provides standalone formatters that work with any app-owned text field.
Available final formats are National, International, E164, and Rfc3966. Invalid input or an unsupported region is returned unchanged.
CountryCodePhoneFormatter.normalizeInput keeps only a leading + and decimal digits for stable raw state. format produces a final value using either a CountryCode or ISO region, while formatAsYouType returns an immediately formatted string for non-text-field use. For editable fields, prefer CountryCodePhoneVisualTransformation or the unified state's visualTransformation so formatting never changes the stored raw value.
The Fully Customized Implementation above demonstrates the recommended CountryCodePhoneState integration. It coordinates the raw number, display formatting, validation, detection, and picker state while the application continues to render and own its text field. By default it enables cursor-safe as-you-type formatting, full phone validation, and country detection. Detection updates the picker only for a complete, valid international number beginning with +; national numbers retain the selected country. Automatic matches respect the configured country filter and are not added to recent selections.
| Phone-state API | Responsibility |
|---|---|
rawNumber |
Normalized unformatted value to bind to the application text field. |
updateNumber(input) |
Normalizes input and runs the selected validation/detection processing. |
visualTransformation |
Cursor-aware, display-only country formatting for the application text field. |
formatAsYouType |
Mutable switch controlling whether the visual transformation is active. |
pickerState / pickerConfig |
Shared picker selection and configuration used by rendering and detection. |
selectedCountry |
Currently selected or automatically detected country. |
validation / isValid |
Latest optional validation result and convenience validity value. |
international / e164 |
Ready-to-use normalized formats when validation succeeds. |
| Processing mode | Behavior |
|---|---|
None |
Stores normalized raw input without validation or detection. |
Validate |
Validates against the selected country without changing it. |
DetectCountry |
Detects complete valid international numbers without producing a validation result. |
ValidateAndDetectCountry |
Performs both operations and is the default. |
Choose lighter processing when needed:
val phoneState = rememberCountryCodePhoneState(
processing = CountryCodePhoneProcessing.Validate, // or None, DetectCountry
validationPreset = CountryCodePhoneValidationPreset.PhoneNumber,
pickerConfig = CountryCodePickerConfig(
countryFilter = CountryCodePickerCountryFilter.Supported(
listOf("AU", "NZ", "US"),
),
),
formatAsYouType = true,
)pickerConfig is the single source of truth for both the visible picker list and automatic detection. Pass phoneState.pickerConfig to CountryCodePicker; a detected country outside its filter will never replace the selection.
Do not call formatAsYouType() and save its formatted result into raw field state. Use phoneState.visualTransformation for immediate formatting without delays or cursor jumps. It keeps inserted spaces display-only and preserves cursor mapping.
For custom state management, bind the lower-level validator once and pass each changing number to the required operation:
val phone = CountryCodePhoneValidator(
pickerState = pickerState,
preset = CountryCodePhoneValidationPreset.PhoneNumber,
countryFilter = config.countryFilter,
)
val validationOnly = phone.validate(rawInput)
val detectedCountryOnly = phone.detectCountry(rawInput)
val validationAndDetection = phone.validateAndDetectCountry(rawInput)validate() never changes the picker. The two detection operations update it only for a complete, valid international number beginning with +, respect supported or unsupported country filters, and do not add automatic matches to recent user selections. National numbers retain the existing country because they cannot be identified reliably.
val result = CountryCodePhoneValidator.validate(
number = phoneNumber,
country = pickerState.selectedCountry,
preset = CountryCodePhoneValidationPreset.PhoneNumber,
)
when (result.status) {
CountryCodePhoneStatus.VALID -> println(result.e164)
CountryCodePhoneStatus.IMPOSSIBLE -> println("Impossible number length")
CountryCodePhoneStatus.INVALID -> println("Invalid phone number")
else -> Unit
}CountryCodeKit Compose validates locally with its maintained Kotlin Multiplatform port of Google libphonenumber.
Choose only the validation level your app needs:
| Preset | Behavior |
|---|---|
PhoneNumber |
Full country-aware libphonenumber validation; this remains the default. |
PossibleLength |
Accepts numbers with a possible length for the selected country, without requiring an assigned number pattern. |
DigitsOnly |
Accepts ASCII digits only and does not require a valid country. |
DigitsAndPossibleLength |
Requires digits only plus a possible length for the selected country. |
CustomLength(range, digitsOnly) |
Checks your own digit-count range and can optionally reject formatting characters. |
val result = CountryCodePhoneValidator.validate(
number = phoneNumber,
country = pickerState.selectedCountry,
preset = CountryCodePhoneValidationPreset.CustomLength(
range = 7..12,
digitsOnly = true,
),
)CountryCodePhoneResult can provide:
statuse164internationalnormalizedDigitsdetectedCountryisValid
status is one of EMPTY, INVALID_REGION, NOT_A_NUMBER, NON_DIGIT_CHARACTERS, TOO_SHORT, TOO_LONG, IMPOSSIBLE, INVALID, or VALID.
This is structural phone-number validation, not ownership verification. CountryCodeKit Compose does not send SMS messages, place calls, or contact a verification service.
| Platform | Status | Target |
|---|---|---|
| Android | Supported | Kotlin/Android library and sample APK |
| iOS devices | Supported | iosArm64 static framework |
| iOS simulator | Supported | iosSimulatorArm64 static framework |
CountryCodeKit Compose does not publish desktop, standalone JVM, web, watchOS, tvOS, Linux, or Windows targets.
Build the Android sample and run the shared tests/framework checks:
./gradlew :sample:composeApp:assembleDebug
./gradlew iosSimulatorArm64Test
./gradlew linkDebugFrameworkIosSimulatorArm64For iOS, open sample/iosApp/CountryCodeKitSample.xcodeproj in Xcode and run the CountryCodeKitSample scheme on an iPhone simulator or device. Xcode builds and embeds the shared Compose framework automatically.
The sample demonstrates every picker style and live validation. The test suite checks the complete country metadata set, bundled flag coverage, shared calling codes, search, recents, optional alphabetical sections, picker state, phone validation, and Compose interactions.
UPSTREAMS.properties records the exact upstream revisions used by each release:
- Google libphonenumber for calling codes, parsing, formatting, and validation metadata under Apache License 2.0.
- flag-icons for SVG flag artwork under the MIT License. CountryCodeKit Compose converts reviewed source artwork into Android/iOS-compatible PNG resources.
- libphonenumber-kotlin as the original Kotlin port baseline under Apache License 2.0.
Run the sync scripts against reviewed local upstream checkouts, update the pins, run the complete suite, and publish a new CountryCodeKit Compose version. A weekly workflow checks whether pinned sources have advanced. Vendored-source changes must never be shipped silently in an existing artifact version.
See third_party/ for complete notices and provenance.
CountryCodeKit Compose is available under the MIT License. Bundled third-party data, source ports, and artwork retain their respective licenses described in the third-party notice directories.




