Address Form with Cascading Selects
Build professional address forms with cascading country, state, and city selects
Learn how to build a complete address form with country → state → city dropdowns using CountryDataAPI's select endpoints.
Overview
This guide shows you how to create cascading select inputs for:
- Country selection - Load all available countries
- State/Province selection - Filtered by selected country
- City selection - Filtered by selected state
The select endpoints are optimized for forms, returning only id and name fields for minimal payload size and maximum performance.
Prerequisites
- A CountryDataAPI key (Get one here)
- Basic knowledge of JavaScript and HTML
- Modern web browser with fetch API support
Step 1: Load Countries
First, load all countries when your page loads. Countries rarely change, so you can cache this data in localStorage for better performance.
const API_KEY = 'your-api-key';
const BASE_URL = 'https://api.countrydataapi.com/v1';
async function loadCountries() {
try {
const response = await fetch(
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=en`
);
const { success, data, error } = await response.json();
if (!success) {
console.error('API Error:', error);
return;
}
const countrySelect = document.getElementById('country');
data.forEach(country => {
const option = new Option(country.name, country.id);
countrySelect.add(option);
});
} catch (err) {
console.error('Network Error:', err);
}
}
// Call on page load
document.addEventListener('DOMContentLoaded', loadCountries);
Tip: Cache the countries list in localStorage to avoid repeated API calls. Countries don't change frequently!
Step 2: Load States When Country Changes
When a user selects a country, load the corresponding states/provinces and reset the city dropdown.
document.getElementById('country').addEventListener('change', async (e) => {
const countryId = e.target.value;
const stateSelect = document.getElementById('state');
const citySelect = document.getElementById('city');
// Reset state and city dropdowns
stateSelect.innerHTML = '<option value="">Select state...</option>';
citySelect.innerHTML = '<option value="">Select city...</option>';
stateSelect.disabled = true;
citySelect.disabled = true;
if (!countryId) return;
try {
const response = await fetch(
`${BASE_URL}/select/states?apikey=${API_KEY}&country=${countryId}&lang=en`
);
const { success, data, error } = await response.json();
if (!success) {
console.error('API Error:', error);
return;
}
data.forEach(state => {
stateSelect.add(new Option(state.name, state.id));
});
stateSelect.disabled = false;
} catch (err) {
console.error('Network Error:', err);
}
});
Step 3: Load Cities When State Changes
Finally, load cities when a state is selected.
document.getElementById('state').addEventListener('change', async (e) => {
const stateId = e.target.value;
const citySelect = document.getElementById('city');
// Reset city dropdown
citySelect.innerHTML = '<option value="">Select city...</option>';
citySelect.disabled = true;
if (!stateId) return;
try {
const response = await fetch(
`${BASE_URL}/select/cities?apikey=${API_KEY}&state=${stateId}&lang=en`
);
const { success, data, error } = await response.json();
if (!success) {
console.error('API Error:', error);
return;
}
data.forEach(city => {
citySelect.add(new Option(city.name, city.id));
});
citySelect.disabled = false;
} catch (err) {
console.error('Network Error:', err);
}
});
Complete HTML Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Address Form - CountryDataAPI</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
select {
width: 100%;
padding: 10px;
border: 2px solid #e2e8f0;
border-radius: 6px;
font-size: 16px;
background-color: white;
cursor: pointer;
}
select:disabled {
background-color: #f7fafc;
cursor: not-allowed;
opacity: 0.6;
}
select:focus {
outline: none;
border-color: #3b82f6;
}
button {
background-color: #3b82f6;
color: white;
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
width: 100%;
}
button:disabled {
background-color: #cbd5e0;
cursor: not-allowed;
}
</style>
</head>
<body>
<h1>Address Form</h1>
<form id="address-form">
<div class="form-group">
<label for="country">Country *</label>
<select id="country" name="country" required>
<option value="">Select country...</option>
</select>
</div>
<div class="form-group">
<label for="state">State/Province *</label>
<select id="state" name="state" required disabled>
<option value="">Select state...</option>
</select>
</div>
<div class="form-group">
<label for="city">City *</label>
<select id="city" name="city" required disabled>
<option value="">Select city...</option>
</select>
</div>
<div class="form-group">
<label for="street">Street Address *</label>
<input type="text" id="street" name="street" required
style="width: 100%; padding: 10px; border: 2px solid #e2e8f0; border-radius: 6px;">
</div>
<button type="submit">Submit Address</button>
</form>
<script src="address-form.js"></script>
</body>
</html>
Form Submission
Handle the form submission to get the selected values:
document.getElementById('address-form').addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const address = {
country: formData.get('country'),
state: formData.get('state'),
city: formData.get('city'),
street: formData.get('street')
};
console.log('Address submitted:', address);
// Send to your backend or process as needed
// fetch('/api/save-address', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(address)
// });
});
Token Usage
This implementation is highly efficient with token usage:
- 1 token for loading countries (once per page load, can be cached)
- 1 token per state lookup (when user selects a country)
- 1 token per city lookup (when user selects a state)
Example: A user filling out the form completely uses only 3 tokens total.
Optimization Tips
1. Cache Country Data
Countries don't change often. Cache them in localStorage:
async function loadCountries() {
const cached = localStorage.getItem('countries');
if (cached) {
const data = JSON.parse(cached);
populateCountrySelect(data);
return;
}
const response = await fetch(
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=en`
);
const { success, data } = await response.json();
if (success) {
localStorage.setItem('countries', JSON.stringify(data));
populateCountrySelect(data);
}
}
function populateCountrySelect(countries) {
const select = document.getElementById('country');
countries.forEach(country => {
select.add(new Option(country.name, country.id));
});
}
2. Add Loading States
Show visual feedback while loading data:
async function loadStates(countryId) {
const stateSelect = document.getElementById('state');
stateSelect.innerHTML = '<option>Loading...</option>';
stateSelect.disabled = true;
// ... fetch states
stateSelect.disabled = false;
}
3. Debounce Search Input
If you add a searchable dropdown, debounce the input:
let debounceTimer;
searchInput.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
filterOptions(e.target.value);
}, 300);
});
4. Handle Errors Gracefully
Always provide user-friendly error messages:
try {
// API call
} catch (err) {
const select = document.getElementById('state');
select.innerHTML = '<option>Error loading states. Please try again.</option>';
}
Multi-language Support
The API supports multiple languages. Change the lang parameter:
// Spanish
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=es`
// Portuguese
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=pt`
// French
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=fr`
// German
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=de`
// Italian
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=it`
Next Steps
- React Integration - Custom React hooks for address forms
- Vue Integration - Vue 3 composables for address forms
- Select Countries Reference - API endpoint documentation
- Select States Reference - API endpoint documentation
- Select Cities Reference - API endpoint documentation
Need Help?
If you have questions or need assistance:
- Check our Error Codes documentation
- Contact support through your account dashboard
- Join our developer community
Pro Tip: For production applications, consider implementing a custom dropdown component with search functionality to handle countries with many states/cities. Libraries like Select2, Choices.js, or custom autocomplete can significantly improve UX.