npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-country-state-city-picker

v0.2.1

Published

Country, state, and city picker for React

Readme

react-country-state-city-picker

Drop-in country → state → city picker for React. Always up-to-date geodata, zero dependencies, auto dark/light mode, fully accessible out of the box.

npm version npm downloads license TypeScript React



📦 Installation

# bun (recommended)
bun add react-country-state-city-picker

# npm
npm install react-country-state-city-picker

# yarn
yarn add react-country-state-city-picker

Requires React 18+. No additional setup needed.


🚀 Quick Start

Composite picker (all three levels)

import { CountryStateCityPicker } from 'react-country-state-city-picker'
import type { PickerSelection } from 'react-country-state-city-picker'

export default function AddressForm() {
	const handleSelect = (selection: PickerSelection) => {
		console.log(selection.country?.name) // "India"
		console.log(selection.state?.name) // "Maharashtra"
		console.log(selection.city?.name) // "Mumbai"
	}

	return <CountryStateCityPicker onSelect={handleSelect} />
}

Individual pickers

import { useState } from 'react'
import {
	CountryPicker,
	StatePicker,
	CityPicker,
} from 'react-country-state-city-picker'
import type { Country, State, City } from 'react-country-state-city-picker'

export default function AddressForm() {
	const [country, setCountry] = useState<Country | null>(null)
	const [state, setState] = useState<State | null>(null)
	const [city, setCity] = useState<City | null>(null)

	return (
		<>
			<CountryPicker value={country} onChange={setCountry} />
			<StatePicker
				value={state}
				onChange={setState}
				countryCode={country?.isoCode}
			/>
			<CityPicker
				value={city}
				onChange={setCity}
				countryCode={country?.isoCode}
				stateCode={state?.isoCode}
			/>
		</>
	)
}

✨ Features

| Feature | Details | | ---------------------------- | ---------------------------------------------------------------------------- | | 🌐 Live geodata | Countries, states, and cities via geocoded.me API | | 🌑 Dark & light mode | Auto-detects system color scheme; override with a single prop | | ♿ Accessibility-first | ARIA roles, live regions, dynamic labels, keyboard navigation, focus traps | | 🌍 Internationalization | 28 customizable label strings — ship in any language | | 🎨 Fully themeable | 22 design tokens covering every pixel of the UI | | 🔌 Render props | Replace trigger, row, search, or empty state with your own UI | | 🪝 Headless hooks | useCountries, useStates, useCities — build your own UI from scratch | | 🔗 Cascade or standalone | Use all three pickers together or any single one in isolation | | ⚡ Smart caching | LRU cache with in-flight deduplication — no duplicate network requests | | 🔁 Retry logic | 3 attempts with exponential backoff on network failures | | 🧪 Testable | testID on every interactive element | | 💙 TypeScript-first | Strict types for all components, hooks, themes, and labels | | 📦 Zero dependencies | No third-party UI library required |


📖 API Reference

<CountryStateCityPicker>

All-in-one component that manages the full country → state → city cascade.

| Prop | Type | Default | Description | | --------------- | ------------------------------------------ | ---------------- | ------------------------------------------------------- | | onSelect | (selection: PickerSelection) => void | — | Called whenever any level changes | | defaultValue | Partial<PickerSelection> | — | Pre-selected values on mount | | theme | Partial<PickerTheme> | auto | Override any design token (falls back to system theme) | | labels | Partial<PickerLabels> | DEFAULT_LABELS | Override any label string | | testID | string | — | Base test ID — suffixed per field (e.g. base-country) | | style | CSSProperties | — | Outer container style | | className | string | — | Outer container class name | | renderTrigger | (props: TriggerRenderProps) => ReactNode | — | Replace the trigger button UI | | renderItem | (props: ItemRenderProps) => ReactNode | — | Replace each dropdown row | | renderSearch | (props: SearchRenderProps) => ReactNode | — | Replace the search input | | renderEmpty | (props: EmptyRenderProps) => ReactNode | — | Replace the empty state |


<CountryPicker>

| Prop | Type | Default | Description | | --------------- | ------------------------------------------ | ---------------- | ---------------------------------- | | value | Country \| null | — | Currently selected country | | onChange | (country: Country) => void | — | Called when user selects a country | | theme | Partial<PickerTheme> | auto | Design tokens | | labels | Partial<PickerLabels> | DEFAULT_LABELS | Label strings | | testID | string | — | Test identifier | | style | CSSProperties | — | Container style | | className | string | — | Container class name | | renderTrigger | (props: TriggerRenderProps) => ReactNode | — | Custom trigger UI | | renderItem | (props: ItemRenderProps) => ReactNode | — | Custom row UI | | renderSearch | (props: SearchRenderProps) => ReactNode | — | Custom search UI | | renderEmpty | (props: EmptyRenderProps) => ReactNode | — | Custom empty state |


<StatePicker>

All props from CountryPicker plus:

| Prop | Type | Default | Description | | ------------- | ----------------------------- | ------- | ---------------------------------------------- | | countryCode | string \| null \| undefined | — | ISO2 country code to load states for | | onNoStates | () => void | — | Called when the selected country has no states | | placeholder | string | — | Overrides the default placeholder text |


<CityPicker>

All props from StatePicker plus:

| Prop | Type | Default | Description | | --------------- | ----------------------------- | ------- | --------------------------------------- | | stateCode | string \| null \| undefined | — | ISO2 state code to load cities for | | notApplicable | boolean | false | Force the field into N/A state manually |


🌑 Dark & Light Mode

Auto-detect system theme

The library reads prefers-color-scheme automatically. No configuration needed — it switches between DEFAULT_THEME (light) and DARK_THEME (dark) on its own.

Manual override

import { DARK_THEME, DEFAULT_THEME } from 'react-country-state-city-picker'

// Force dark
<CountryStateCityPicker theme={DARK_THEME} onSelect={handleSelect} />

// Force light
<CountryStateCityPicker theme={DEFAULT_THEME} onSelect={handleSelect} />

Custom brand theme

Override only the tokens you need — everything else falls back to the system default:

const brandTheme = {
  dropdownBackground: '#1e1b4b',
  titleColor: '#a5b4fc',
  searchBackground: '#312e81',
  rowTextColor: '#e0e7ff',
  labelColor: '#a5b4fc',
  borderColor: '#4f46e5',
  triggerBackground: '#1e1b4b',
  valueTextColor: '#e0e7ff',
  chevronColor: '#6366f1',
}

<CountryStateCityPicker theme={brandTheme} onSelect={handleSelect} />

| Token | Description | | ------------------------ | --------------------------- | | dropdownBackground | Dropdown panel background | | titleColor | Dropdown title text | | searchBackground | Search input background | | searchTextColor | Search input text | | searchPlaceholderColor | Search placeholder text | | rowTextColor | Primary text in list rows | | rowSubTextColor | Secondary text in list rows | | rowHoverBackground | Row hover state | | separatorColor | Row separator line | | emptyTextColor | Empty state text | | labelColor | Field label above trigger | | borderColor | Trigger border | | triggerBackground | Trigger background | | disabledBackground | Disabled trigger background | | disabledBorderColor | Disabled trigger border | | hoverBackground | Trigger hover state | | focusRingColor | Keyboard focus ring | | valueTextColor | Selected value text | | placeholderColor | Trigger placeholder text | | chevronColor | Chevron icon | | chevronDisabledColor | Chevron when disabled | | loadingColor | Loading spinner |


🌍 Internationalization

Override any label string to ship in any language:

import { CountryStateCityPicker } from 'react-country-state-city-picker'

const spanishLabels = {
  countryLabel: 'País',
  stateLabel: 'Estado / Provincia',
  cityLabel: 'Ciudad',
  countryTitle: 'Selecciona un país',
  stateTitle: 'Selecciona un estado',
  cityTitle: 'Selecciona una ciudad',
  searchPlaceholder: 'Buscar…',
  noResults: 'Sin resultados',
  countryPlaceholder: 'Seleccionar país',
  statePlaceholder: 'Seleccionar estado',
  cityPlaceholder: 'Seleccionar ciudad',
}

<CountryStateCityPicker labels={spanishLabels} onSelect={handleSelect} />
type PickerLabels = {
	// Field labels
	countryLabel: string
	stateLabel: string
	cityLabel: string

	// Dropdown titles
	countryTitle: string
	stateTitle: string
	cityTitle: string

	// Placeholder text
	countryPlaceholder: string
	statePlaceholder: string
	cityPlaceholder: string

	// Disabled-state placeholders
	stateDisabledPlaceholder: string
	cityDisabledPlaceholder: string

	// N/A state
	stateNotApplicable: string
	cityNotApplicable: string

	// Disabled hints (shown as tooltip)
	stateDisabledHint: string
	cityDisabledHint: string

	// Search
	searchPlaceholder: string
	noResults: string

	// Loading & error (called with runtime values)
	loadingLabel: (field: string) => string
	errorLabel: (field: string) => string
	fallbackPlaceholder: (field: string) => string

	// Accessibility
	closeDropdown: (title: string) => string
	searchAccessibilityLabel: (title: string) => string
	openPickerHint: (label: string) => string
	selectedValueLabel: (label: string, value: string) => string
	fallbackInputLabel: (label: string) => string
	fallbackInputHint: (label: string) => string
}

🔌 Render Props

Take complete control of any part of the UI while keeping the library's data-fetching, caching, and cascade logic intact.

Custom trigger

<CountryPicker
	value={country}
	onChange={setCountry}
	renderTrigger={({
		label,
		displayValue,
		placeholder,
		isLoading,
		isDisabled,
		onPress,
	}) => (
		<button onClick={onPress} disabled={isDisabled} className="my-trigger">
			<span>{label}</span>
			<span>{displayValue ?? placeholder}</span>
			{isLoading && <span className="spinner" />}
		</button>
	)}
/>

Custom row

<CountryPicker
	value={country}
	onChange={setCountry}
	renderItem={({ label, value, onSelect }) => (
		<button onClick={onSelect} className="my-row">
			{label}
			<span className="iso-code">{value}</span>
		</button>
	)}
/>

Custom search

<CountryPicker
	value={country}
	onChange={setCountry}
	renderSearch={({ value, placeholder, onChange }) => (
		<div className="my-search">
			<span>🔍</span>
			<input
				value={value}
				onChange={(e) => onChange(e.target.value)}
				placeholder={placeholder}
			/>
		</div>
	)}
/>

Custom empty state

<CountryPicker
	value={country}
	onChange={setCountry}
	renderEmpty={({ query }) => <p>No results for "{query}"</p>}
/>

All four render props work on CountryPicker, StatePicker, CityPicker, and CountryStateCityPicker.


🪝 Hooks

Use the headless hooks to build entirely custom UIs with the library's caching layer.

useCountries()

import { useCountries } from 'react-country-state-city-picker'

const { data, isLoading, error } = useCountries()
// data: Country[]  — [ { name, isoCode, flag, currency }, … ]

useStates(countryCode)

import { useStates } from 'react-country-state-city-picker'

const { data, isLoading, error } = useStates('IN')
// data: State[]  — [ { name, isoCode, countryCode }, … ]

useCities(countryCode, stateCode)

import { useCities } from 'react-country-state-city-picker'

const { data, isLoading, error } = useCities('IN', 'MH')
// data: City[]  — [ { name, stateCode, countryCode }, … ]

usePresetSelection(input)

Resolve backend-stored strings (country name or ISO2 code) back into typed objects on load.

import { usePresetSelection } from 'react-country-state-city-picker'

// Backend gives you raw strings — resolve them to full objects
const { selection, isLoading } = usePresetSelection({
  country: 'IN',         // ISO2 code or display name
  state: 'Maharashtra',  // name or ISO2 code
  city: 'Mumbai',
})

<CountryStateCityPicker defaultValue={selection} onSelect={handleSelect} />

usePickerTheme(override?)

import { usePickerTheme } from 'react-country-state-city-picker'

// Resolves to DARK_THEME or DEFAULT_THEME based on prefers-color-scheme,
// then merges any override tokens on top
const theme = usePickerTheme({ borderColor: '#6366f1' })

♿ Accessibility

The picker is built accessibility-first:

  • ARIA roles — triggers are role="button" with aria-haspopup="listbox", dropdowns use role="listbox"
  • Live regions — loading and error states announced via aria-live="polite"
  • Dynamic labels — screen readers hear "Country: India" (not just "Button")
  • Disabled hints — when a field is locked, screen readers explain why (e.g. "Select a country first")
  • Keyboard navigation — full keyboard support in dropdowns (Arrow keys, Enter, Escape)
  • Focus management — search input auto-focuses when dropdown opens; focus returns to trigger on close
  • Hidden decorative elements — chevron and spinner are aria-hidden

All labels and hints are customizable via the labels prop so you can localize them too.


🧪 Testing

Every interactive element exposes a testID. Pass a base string and each sub-element gets a suffixed ID:

<CountryStateCityPicker testID="address" onSelect={handleSelect} />

// Resolves to:
// address-country   — country trigger button
// address-state     — state trigger button
// address-city      — city trigger button
// Example with Testing Library
const { getByTestId } = render(<AddressForm />)

await userEvent.click(getByTestId('address-country-trigger'))
// … interact with the dropdown

🏗️ Architecture

CountryStateCityPicker
├── CountryPicker
│   ├── useCountries()          ← LRU cache, retry, dedup
│   └── PickerDropdown
│       ├── search input        ← debounced, accent-normalized
│       └── listbox rows
├── StatePicker
│   └── useStates(countryCode)
└── CityPicker
    └── useCities(countryCode, stateCode)

Caching strategy:

| Resource | Cache size | ~Memory | | --------- | ---------- | ------- | | Countries | 1 entry | ~10 KB | | States | 20 entries | ~30 KB | | Cities | 10 entries | ~300 KB |

In-flight deduplication prevents duplicate API calls when multiple components mount simultaneously.


📐 TypeScript

All types are exported from the package root:

import type {
	Country,
	State,
	City,
	PickerSelection,
	PickerTheme,
	PickerLabels,
	TriggerRenderProps,
	ItemRenderProps,
	SearchRenderProps,
	EmptyRenderProps,
	PickerRenderProps,
} from 'react-country-state-city-picker'
type Country = {
	name: string
	isoCode: string
	flag: string // emoji flag
	currency?: string
}

type State = {
	name: string
	isoCode: string
	countryCode: string
}

type City = {
	name: string
	stateCode: string
	countryCode: string
}

type PickerSelection = {
	country: Country | null
	state: State | null
	city: City | null
}

🤝 Contributing

See CONTRIBUTING.md for the development workflow and how to send a pull request.


📄 License

MIT © Shan Kulkarni