Address form field order by country
A Japanese customer’s delivery address arrives with the prefecture in the “State” field, the building name appended to the street line, and the postcode in a box that rejected it twice for having a hyphen. A German customer is asked for a state that does not exist in their address. A British customer’s postcode fails a five-digit validation.
The form was designed once, against one country’s conventions, and every other country is being asked to fit into it.
Root cause: an address is a country’s data structure, not a universal one
There is no international address schema. What exists is a set of national conventions that differ in which components exist, what they are called, what order they appear in, which are mandatory, and how each is formatted.
Three differences account for most failures.
Component presence. A first-level administrative division is essential in the United States, Japan, Brazil and Australia; meaningless in Germany, the Netherlands and most of Europe. Rendering it always produces a required field a German customer cannot fill; omitting it always produces an address a Japanese courier cannot use.
Naming. The same slot is a state, a province, a county, a prefecture or a region. Translating the word “State” into German does not help, because the problem is not the language — it is that the concept does not apply.
Order. Most of the world writes an address smallest unit first; Japan, China and Korea write it largest first. That is a reversal rather than a rearrangement, which is why a fixed field order cannot be made to work for both by relabelling.
Minimal reproducible example
// One form, one country's assumptions
<input name="street" placeholder="Street address" />
<input name="city" placeholder="City" />
<select name="state" required>{US_STATES.map(...)}</select>
<input name="zip" pattern="\d{5}" required placeholder="ZIP code" />
Every line is a decision that is correct in one country. The pattern rejects the United Kingdom, Canada and the Netherlands; the required state blocks most of Europe; the order is wrong for Japan.
The fix: a schema per country, and the country first
type AddressField = {
name: 'street' | 'street2' | 'city' | 'region' | 'postalCode';
labelKey: string; // translated, e.g. 'address.region.prefecture'
required: boolean;
pattern?: RegExp;
autocomplete: string; // the standard token, not a country word
};
export const ADDRESS_SCHEMAS: Record<string, AddressField[]> = {
US: [
{ name: 'street', labelKey: 'address.street', required: true, autocomplete: 'address-line1' },
{ name: 'city', labelKey: 'address.city', required: true, autocomplete: 'address-level2' },
{ name: 'region', labelKey: 'address.region.state', required: true, autocomplete: 'address-level1' },
{ name: 'postalCode', labelKey: 'address.postal.zip', required: true,
pattern: /^\d{5}(-\d{4})?$/, autocomplete: 'postal-code' },
],
DE: [
{ name: 'street', labelKey: 'address.street', required: true, autocomplete: 'address-line1' },
{ name: 'postalCode', labelKey: 'address.postal.plz', required: true,
pattern: /^\d{5}$/, autocomplete: 'postal-code' },
{ name: 'city', labelKey: 'address.city', required: true, autocomplete: 'address-level2' },
],
JP: [
{ name: 'postalCode', labelKey: 'address.postal.jp', required: true,
pattern: /^\d{3}-?\d{4}$/, autocomplete: 'postal-code' },
{ name: 'region', labelKey: 'address.region.prefecture', required: true, autocomplete: 'address-level1' },
{ name: 'city', labelKey: 'address.city', required: true, autocomplete: 'address-level2' },
{ name: 'street', labelKey: 'address.street.jp', required: true, autocomplete: 'address-line1' },
],
};
export const GENERIC: AddressField[] = [
{ name: 'street', labelKey: 'address.street', required: true, autocomplete: 'address-line1' },
{ name: 'street2', labelKey: 'address.street2', required: false, autocomplete: 'address-line2' },
{ name: 'city', labelKey: 'address.city', required: true, autocomplete: 'address-level2' },
{ name: 'region', labelKey: 'address.region.generic', required: false, autocomplete: 'address-level1' },
{ name: 'postalCode', labelKey: 'address.postal.generic', required: false, autocomplete: 'postal-code' },
];
The schema carries the order by being an array, which is what makes Japan expressible without a special case in the component. The label is a translation key rather than a word, so “Prefecture” is translated into every interface language while remaining the correct concept for Japan.
The generic fallback is the part that scales
Curating schemas for every territory is a project without an end. Curating the ten or twenty you actually ship to, and falling back to a generic international form everywhere else, gets nearly all of the benefit.
The generic form must genuinely be generic: street lines, a city, an optional region with a neutral label, an optional postcode with no pattern. What it must not be is one country’s form used as a default, which is how an American form ends up in front of a customer in a country nobody thought about.
Two rules keep the fallback honest. Nothing beyond street and city is required, because in some territory each of the other fields is absent. And no pattern validation runs, because a pattern that fits nothing in particular rejects real addresses. A generic form accepts more than it should, which is the correct failure direction for an address: a courier can interpret an oddly formatted address, and cannot deliver one that was never submitted.
Formatting the address back out
Collecting an address correctly is half the job; rendering it is the other half, and it uses the same per-country data in the opposite direction.
An address appears in several places after collection — an order confirmation, a shipping label, an invoice, a customer record — and each of them needs the components assembled into lines according to the destination country’s convention rather than the reader’s. A German address printed in American order is as unusable to a German courier as it would be collected that way.
The assembly rule is a per-country template over the same component names, which means the schema that drives the form can drive the rendering too:
const LABEL_TEMPLATES: Record<string, string[]> = {
US: ['{recipient}', '{street}', '{city}, {region} {postalCode}', '{country}'],
DE: ['{recipient}', '{street}', '{postalCode} {city}', '{country}'],
JP: ['{postalCode}', '{region}{city}', '{street}', '{recipient}'],
};
Two details matter in practice. The country line is omitted for a domestic address and required for an international one, so the template needs to know where the parcel is going from as well as to. And the label should be in the destination’s own language and script where possible, because the person reading it is a postal worker in that country — an address for Japan is best printed in Japanese, with a Latin transliteration underneath rather than instead.
That last point is the one that surprises teams whose interface is fully localized: the shipping label is the one surface where the reader’s locale is irrelevant.
Verification
test.each([
['US', ['street', 'city', 'region', 'postalCode']],
['DE', ['street', 'postalCode', 'city']],
['JP', ['postalCode', 'region', 'city', 'street']],
])('%s renders fields in the local order', (country, expected) => {
const { getAllByRole } = render(<AddressForm country={country} />);
expect(getAllByRole('textbox').map((i) => i.getAttribute('name'))).toEqual(expected);
});
test.each([
['US', '94103', true], ['US', 'SW1A 1AA', false],
['GB', 'SW1A 1AA', true], ['GB', '94103', false],
['NL', '1012 AB', true],
])('%s postcode %s valid=%s', (country, code, ok) => {
expect(isValidPostalCode(country, code)).toBe(ok);
});
The Dutch row is worth keeping specifically: 1012 AB contains a space and letters, and it is the value that most often reveals a validation rule written against digits.
When to escalate
If addresses validate and still fail delivery, the problem may be the join rather than the fields. An address stored as separate components has to be assembled into lines for a label, and the assembly order is per country too — the same schema can drive it.
If a courier or payment provider rejects an address your form accepted, compare their required fields against the schema. Providers publish per-country requirements, and their rules are the operative ones for anything that ships.
If a customer insists their address does not fit any field, believe them. Rural addresses, addresses without street names, and PO-box-only territories all exist, and a free-text line that is passed through untouched is the pressure valve that keeps them able to buy.
FAQ
Should the country be a selector or derived from the locale?
A selector, defaulting to a guess. Locale and country are different questions — an American living in Berlin has an English interface and a German address — and deriving one from the other guarantees a wrong default for exactly the people most likely to notice.
Where do the schemas come from?
Published address-format data derived from postal authorities covers most territories, and payment providers expose per-country requirements through their APIs. For the countries you ship to most, curating by hand from either source is a day of work and stays correct for years.
Do I need to validate postcodes at all?
Lightly. A pattern per country catches transposed digits and pasted junk, which is worth having. What is not worth having is a strict pattern that rejects a valid but unusual code — a warning that lets the customer proceed is a better shape than a block.
How should the address be stored?
As components plus the country code, not as a single formatted string. Components can be re-rendered in any convention, sent to any provider, and corrected field by field; a formatted blob can only be reparsed by guessing.
Related
- Internationalizing Forms & Input — the country-decides-structure model this applies.
- Phone number input and E.164 storage — the same country-driven treatment for another field.
- Name fields that assume first and last — the field where the fix is to ask for less.
- RTL & Bidirectional Layout Engineering — laying the same form out in a mirrored direction.
Part of Internationalizing Forms & Input.