# Address input

Package: @commenda/pathfinder@0.1.0
Page: /docs/components/address-input
Figma: https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=286-2

```tsx
import { AddressInput } from '@commenda/pathfinder/address-input';

export const AddressInputDefault = () => (
  <AddressInput
    label="Registered address"
    name="address"
    defaultValue={{
      country: 'US',
      addressLine1: '',
      addressLine2: '',
      city: '',
      region: '',
      postalCode: '',
    }}
    description="Enter the address shown on your registration documents."
  />
);
```


## Installation [#installation]

Import the component in your screen:

```tsx
import {
  AddressInput,
  type AddressValue,
} from '@commenda/pathfinder/address-input';
```

Load the stylesheet once in your app root, after host styles:

```tsx
import '@commenda/pathfinder/styles.css';
```

Pathfinder ships precompiled CSS; no Tailwind setup is required. In Next.js App Router, put interactive state and callbacks in a client component. See [installation](/docs/getting-started/installation) for the workspace setup.

## API reference [#api-reference]

| Prop | Type | Requirement | Behavior |
| --- | --- | --- | --- |
| `label` | `string` | conditional | Accessible group name; one is required |
| `aria-label` | `string` | conditional | Accessible group name; one is required |
| `hideLabel` | `boolean` | optional | Visually hides the group label |
| `value` | `AddressValue` | conditional | Controlled or uncontrolled complete address; defaults to empty fields |
| `defaultValue` | `AddressValue` | optional | Controlled or uncontrolled complete address; defaults to empty fields |
| `onValueChange` | `((value: AddressValue) => void)` | conditional | Complete value on every edit; required with `value` |
| `countryConfig` | `AddressCountryConfig` | optional | Defaults to Kakashi's 199 named supported countries |
| `regionConfig` | `{ options: readonly ChoiceOption[]; }` | optional | Overrides the country-based searchable state/region selector |
| `suggestionsConfig` | `AddressSuggestionsConfig` | optional | Optional address lookup; manual entry always remains available |
| `requiredFields` | `readonly ("country" \| "addressLine1" \| "city" \| "region" \| "postalCode")[]` | optional | Per-field native required validation; line 2 is always optional |
| `fieldErrors` | `Partial<Record<keyof AddressValue, string>>` | optional | Field-specific messages displayed immediately |
| `disableConfig` | `{ isDisabled: boolean; reason?: string; }` | optional | Disables all fields and excludes them from native submission |
| `readOnly` | `boolean` | optional | Preserves readable, copyable, submittable values |
| `description` | `string` | optional | Group help text |
| `id` | `string` | optional | Group ID, field-name prefix, and native form association |
| `name` | `string` | optional | Group ID, field-name prefix, and native form association |
| `form` | `string` | optional | Group ID, field-name prefix, and native form association |
| `className` | `string` | optional | Root styling and additional accessible description |
| `aria-describedby` | `string` | optional | Root styling and additional accessible description |

### value fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `country` | `string` | Yes |  |
| `addressLine1` | `string` | Yes |  |
| `addressLine2` | `string` | Yes |  |
| `city` | `string` | Yes |  |
| `region` | `string` | Yes |  |
| `postalCode` | `string` | Yes |  |

### defaultValue fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `country` | `string` | Yes |  |
| `addressLine1` | `string` | Yes |  |
| `addressLine2` | `string` | Yes |  |
| `city` | `string` | Yes |  |
| `region` | `string` | Yes |  |
| `postalCode` | `string` | Yes |  |

### countryConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `allowedCountries` | `readonly ("AD" \| "AE" \| "AF" \| "AG" \| "AL" \| "AM" \| "AO" \| "AR" \| "AT" \| "AU" \| "AZ" \| "BA" \| "BB" \| "BD" \| "BE" \| "BF" \| "BG" \| "BH" \| "BI" \| "BJ" \| "BN" \| "BO" \| "BR" \| "BS" \| "BT" \| "BW" \| "BY" \| "BZ" \| "CA" \| "CD" \| "CF" \| "CG" \| "CH" \| "CI" \| "CK" \| "CL" \| "CM" \| "CN" \| "CO" \| "CR" \| "CU" \| "CV" \| "CY" \| "CZ" \| "DE" \| "DJ" \| "DK" \| "DM" \| "DO" \| "DZ" \| "EC" \| "EE" \| "EG" \| "ER" \| "ES" \| "ET" \| "FI" \| "FJ" \| "FM" \| "FR" \| "GA" \| "GB" \| "GD" \| "GE" \| "GH" \| "GM" \| "GN" \| "GQ" \| "GR" \| "GT" \| "GW" \| "GY" \| "HN" \| "HR" \| "HT" \| "HU" \| "ID" \| "IE" \| "IL" \| "IN" \| "IQ" \| "IR" \| "IS" \| "IT" \| "JE" \| "JM" \| "JO" \| "JP" \| "KE" \| "KG" \| "KH" \| "KI" \| "KM" \| "KN" \| "KR" \| "KW" \| "KY" \| "KZ" \| "LA" \| "LB" \| "LC" \| "LI" \| "LK" \| "LR" \| "LS" \| "LT" \| "LU" \| "LV" \| "LY" \| "MA" \| "MC" \| "MD" \| "ME" \| "MG" \| "MH" \| "MK" \| "ML" \| "MM" \| "MN" \| "MR" \| "MT" \| "MU" \| "MV" \| "MW" \| "MX" \| "MY" \| "MZ" \| "NA" \| "NE" \| "NG" \| "NI" \| "NL" \| "NO" \| "NP" \| "NR" \| "NZ" \| "OM" \| "PA" \| "PE" \| "PG" \| "PH" \| "PK" \| "PL" \| "PR" \| "PT" \| "PW" \| "PY" \| "QA" \| "RO" \| "RS" \| "RU" \| "RW" \| "SA" \| "SB" \| "SC" \| "SD" \| "SE" \| "SG" \| "SI" \| "SK" \| "SL" \| "SM" \| "SN" \| "SO" \| "SR" \| "SS" \| "ST" \| "SV" \| "SY" \| "SZ" \| "TD" \| "TG" \| "TH" \| "TJ" \| "TL" \| "TM" \| "TN" \| "TO" \| "TR" \| "TT" \| "TV" \| "TW" \| "TZ" \| "UA" \| "UG" \| "US" \| "UY" \| "UZ" \| "VA" \| "VC" \| "VE" \| "VG" \| "VN" \| "VU" \| "WS" \| "YE" \| "ZA" \| "ZM" \| "ZW")[]` | No |  |
| `excludedCountries` | `readonly ("AD" \| "AE" \| "AF" \| "AG" \| "AL" \| "AM" \| "AO" \| "AR" \| "AT" \| "AU" \| "AZ" \| "BA" \| "BB" \| "BD" \| "BE" \| "BF" \| "BG" \| "BH" \| "BI" \| "BJ" \| "BN" \| "BO" \| "BR" \| "BS" \| "BT" \| "BW" \| "BY" \| "BZ" \| "CA" \| "CD" \| "CF" \| "CG" \| "CH" \| "CI" \| "CK" \| "CL" \| "CM" \| "CN" \| "CO" \| "CR" \| "CU" \| "CV" \| "CY" \| "CZ" \| "DE" \| "DJ" \| "DK" \| "DM" \| "DO" \| "DZ" \| "EC" \| "EE" \| "EG" \| "ER" \| "ES" \| "ET" \| "FI" \| "FJ" \| "FM" \| "FR" \| "GA" \| "GB" \| "GD" \| "GE" \| "GH" \| "GM" \| "GN" \| "GQ" \| "GR" \| "GT" \| "GW" \| "GY" \| "HN" \| "HR" \| "HT" \| "HU" \| "ID" \| "IE" \| "IL" \| "IN" \| "IQ" \| "IR" \| "IS" \| "IT" \| "JE" \| "JM" \| "JO" \| "JP" \| "KE" \| "KG" \| "KH" \| "KI" \| "KM" \| "KN" \| "KR" \| "KW" \| "KY" \| "KZ" \| "LA" \| "LB" \| "LC" \| "LI" \| "LK" \| "LR" \| "LS" \| "LT" \| "LU" \| "LV" \| "LY" \| "MA" \| "MC" \| "MD" \| "ME" \| "MG" \| "MH" \| "MK" \| "ML" \| "MM" \| "MN" \| "MR" \| "MT" \| "MU" \| "MV" \| "MW" \| "MX" \| "MY" \| "MZ" \| "NA" \| "NE" \| "NG" \| "NI" \| "NL" \| "NO" \| "NP" \| "NR" \| "NZ" \| "OM" \| "PA" \| "PE" \| "PG" \| "PH" \| "PK" \| "PL" \| "PR" \| "PT" \| "PW" \| "PY" \| "QA" \| "RO" \| "RS" \| "RU" \| "RW" \| "SA" \| "SB" \| "SC" \| "SD" \| "SE" \| "SG" \| "SI" \| "SK" \| "SL" \| "SM" \| "SN" \| "SO" \| "SR" \| "SS" \| "ST" \| "SV" \| "SY" \| "SZ" \| "TD" \| "TG" \| "TH" \| "TJ" \| "TL" \| "TM" \| "TN" \| "TO" \| "TR" \| "TT" \| "TV" \| "TW" \| "TZ" \| "UA" \| "UG" \| "US" \| "UY" \| "UZ" \| "VA" \| "VC" \| "VE" \| "VG" \| "VN" \| "VU" \| "WS" \| "YE" \| "ZA" \| "ZM" \| "ZW")[]` | No |  |

### regionConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `options` | `readonly ChoiceOption[]` | Yes |  |

### suggestionsConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `items` | `readonly AddressSuggestion[]` | Yes |  |
| `isLoading` | `boolean` | Yes |  |
| `error` | `string` | No |  |
| `resultsQuery` | `string` | Yes | Query that produced the supplied items; stale results cannot be selected. |
| `onQueryChange` | `(query: string) => void` | Yes |  |

### disableConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `isDisabled` | `boolean` | Yes |  |
| `reason` | `string` | No |  |

`AddressValue` contains six strings: `country` (ISO code), `addressLine1`, `addressLine2`, `city`, `region`, and `postalCode`. A `name="address"` produces fields named `address.country`, `address.addressLine1`, and so on. Uncontrolled values restore their defaults on form reset.

## Countries [#countries]


```tsx
import { AddressInput } from '@commenda/pathfinder/address-input';

export const AddressInputCountries = () => (
  <AddressInput
    label="Business address"
    countryConfig={{ allowedCountries: ['GB', 'US', 'IN', 'SG'] }}
    description="Country order follows Kakashi, regardless of the order in allowedCountries."
  />
);
```


Pathfinder's available country codes and labels are bundled at build time. United States, Canada, India, United Kingdom, Singapore, United Arab Emirates, and Ireland appear first, followed by other countries sorted alphabetically by label. `None`, `Other`, and entries without a label are omitted. Empty `allowedCountries` means all available countries; exclusions always apply. Filtering preserves the canonical order.

Pathfinder owns the supported countries, labels, priority order, and region exclusions in `packages/pathfinder/data/address-countries.json`. Run `pnpm --filter @commenda/pathfinder generate:address-countries` after changing this file. Regional metadata comes from Pathfinder's pinned `country-state-city` build dependency. Generation does not read another workspace package.

## Address suggestions [#address-suggestions]


```tsx
import { useState } from 'react';

import {
  AddressInput,
  type AddressValue,
} from '@commenda/pathfinder/address-input';

export const AddressInputSuggestions = () => {
  const [query, setQuery] = useState('');
  const address: AddressValue = {
    country: 'US',
    addressLine1: '350 Fifth Avenue',
    addressLine2: '',
    city: 'New York',
    region: 'NY',
    postalCode: '10118',
  };

  return (
    <AddressInput
      label="Search or enter an address"
      suggestionsConfig={{
        resultsQuery: query,
        onQueryChange: setQuery,
        isLoading: false,
        items:
          query && '350 fifth avenue'.includes(query.toLowerCase())
            ? [
                {
                  id: 'example',
                  label: address.addressLine1,
                  description: 'New York, NY 10118, United States',
                  address,
                },
              ]
            : [],
      }}
      description="Demo: type 350. You can always enter the address manually."
    />
  );
};
```


The example uses local demonstration data. Production applications supply `suggestionsConfig` with `items`, `isLoading`, `resultsQuery`, `onQueryChange`, and optional `error`. Each suggestion is `{ id, label, description?, address: AddressValue }`.

The application owns requests, debouncing, cancellation, and credentials. Set `resultsQuery` to the query that produced the current items, not the latest input before its request finishes. Stale results are hidden until their query matches the typed street address. Selecting a suggestion fills all six fields. Suggestions outside the allowed country list cannot be selected. Search failure never prevents manual entry.

## Validation [#validation]


```tsx
import { AddressInput } from '@commenda/pathfinder/address-input';

export const AddressInputStates = () => (
  <AddressInput
    label="Required address"
    requiredFields={['country', 'addressLine1', 'city', 'postalCode']}
    fieldErrors={{
      addressLine1: 'Enter the registered street address.',
      postalCode: 'Enter a postal code.',
    }}
  />
);
```


Country changes preserve all entered fields for review. State and region options default to the pinned country-state-city metadata, including Pathfinder's US territory exclusions. Countries with no metadata allow free-text entry. Applications can override the options with `regionConfig`; country-specific validation remains application-owned. Changing a country does not silently erase an address or select a region.

## Design and implementation rules [#design-and-implementation-rules]

- **ADDRESS-001:** Figma 292:9094 and 293:3032 establish the compact field treatment. Keep the approved stable order: country, street, optional line 2, city, region, postal code. Narrow layouts stack the fields.
- **ADDRESS-002:** Suggestions overlay the street field without moving the form. Manual editing remains available when search fails.
- **ADDRESS-003:** Preserve Pathfinder country availability, labels, and priority/alphabetical ordering. There is no private runtime dependency.
- **ADDRESS-004:** Preserve drafts on country changes; default regions follow the bundled metadata and application options override them. The application owns validation and requests.
- **ADDRESS-005:** Line 2 cannot be required. Preserve native autocomplete and form association for each field.
- **ADDRESS-006:** Only results matching the current street query can be selected.

These rules also live alongside the component implementation and are available through the Markdown endpoint, `llms.txt`, and `llms-full.txt`.

### Shared rules [#shared-rules]

Labels activate the first input. Descriptions, disabled reasons, and validation messages stay associated with the field. Error icons center on the first 18px text line, including wrapped messages. Controlled values require callbacks; uncontrolled form resets restore defaults. Public props expose application values, never private primitive or calendar objects. Styles ship precompiled for React 18/19 and Next.js consumers.
