TkxAddressInput
TkxAddressInput is a single component with two complementary input
modes that can coexist in the same form:
- PIN-driven (default) — user types a 6-digit PIN, the component
queries India Post’s free public API (
api.postalpincode.in) and auto-fills city / state / country. - Divisions-driven (opt-in via
divisionsSource, new in v3.20) — render four cascading dropdowns: Country → State / UT → District → per-state-labelled Sub-district. Useful when the user may not know their PIN, when you need stable LGD codes (not just display strings) stored in your DB, or for regions where PIN lookup doesn’t apply.
Both modes coexist — passing divisionsSource adds the dropdowns; PIN
lookup keeps working. The component never fights itself.
PIN-only mode
Section titled “PIN-only mode”{}import { useState } from 'react';import { TkxAddressInput, type AddressValue } from 'tekivex-ui';
export function ShippingAddress() { const [addr, setAddr] = useState<Partial<AddressValue>>({}); return <TkxAddressInput value={addr} onChange={setAddr} label="Shipping address" />;}What you get:
- 6-digit PIN field with
inputMode="numeric"andautoComplete="postal-code" - On 6-digit completion, fetch to
api.postalpincode.in - When a PIN resolves to multiple post offices (common — one PIN can map
to many), a
role="listbox"dropdown for the user to pick - City / State / Country auto-fill from the picked post office
- Two address-line fields below for street / apt
aria-busy,aria-invalid,aria-livewired up
This is the v3.19 behaviour, preserved byte-for-byte in v3.20.
Cascading dropdowns (new in v3.20)
Section titled “Cascading dropdowns (new in v3.20)”{}npm install tekivex-ui tekivex-india-adminimport { useState } from 'react';import { TkxAddressInput, type AddressValue } from 'tekivex-ui';import { lgdSnapshot } from 'tekivex-india-admin';
// Memoise at module scope — a fresh loader on every render would re-fire// the initial countries() fetch.const divisions = lgdSnapshot();
export function CompleteAddress() { const [addr, setAddr] = useState<Partial<AddressValue>>({}); return ( <TkxAddressInput value={addr} onChange={setAddr} divisionsSource={divisions} /> );}The component now renders a cascading row of four dropdowns above the PIN field. The 4th column’s label updates per the selected state — pick Maharashtra and it says “Taluka”; pick Uttar Pradesh and it says “Tehsil”; pick Andhra Pradesh and it says “Mandal”. Bring your own loader if you don’t want the bundled snapshot — see the India-address-form recipe for the full integration including a custom REST-backed loader.
AddressValue shape
Section titled “AddressValue shape”interface AddressValue { // PIN-driven fields pin: string; postOffice?: string; city?: string; // District / city display name state?: string; // State / UT display name country?: string; // Country display name
// Divisions-driven fields (populated when divisionsSource is used) subDistrict?: string; countryCode?: string; // ISO 3166-1 alpha-2, e.g. "IN" stateCode?: string; // ISO 3166-2, e.g. "IN-MH" districtCode?: string; // LGD code or loader-defined subDistrictCode?: string; // LGD code or loader-defined
// User-typed line1?: string; line2?: string;}| Prop | Type | Required | Description |
|---|---|---|---|
value | Partial<AddressValue> | ✓ | Current value (controlled). |
onChange | (v: AddressValue) => void | ✓ | Fires on every change. |
lookup | (pin: string, signal?: AbortSignal) => Promise<PostOffice[]> | Override the India Post default — plug in Smartystreets, Google Places, or your own service. | |
divisionsSource | DivisionsLoader | New in v3.20. When supplied, prepends a Country → State → District → Sub-district cascade above the PIN field. See DivisionsLoader. | |
label | string | Visible label above the picker. | |
showAddressLines | boolean | Show line1 / line2 fields under the PIN row. Defaults to true. | |
disabled | boolean | ||
className | string | Forwarded to the root container. | |
style | CSSProperties | Forwarded to the root container. |
DivisionsLoader interface
Section titled “DivisionsLoader interface”interface AdminDivision { code: string; // Stable code (ISO 3166-2 for states, LGD elsewhere) name: string; // Display name in English / Latin script localName?: string; // Optional name in regional script}
interface DivisionsLoader { countries(signal?: AbortSignal): Promise<AdminDivision[]>; states(countryCode: string, signal?: AbortSignal): Promise<AdminDivision[]>; districts( countryCode: string, stateCode: string, signal?: AbortSignal, ): Promise<AdminDivision[]>; subDistricts( countryCode: string, stateCode: string, districtCode: string, signal?: AbortSignal, ): Promise<AdminDivision[]>; /** Per-state regional label override — returns "Taluka", "Tehsil", * "Mandal", "Block", "Circle", etc. Falls back to "Sub-district". */ subDistrictLabel?(countryCode: string, stateCode: string): string;}Every method receives an AbortSignal so in-flight requests get
cancelled when the user changes a higher level. The interface is
deliberately small — any object that satisfies this shape is a valid
loader, whether it reads from a static snapshot, a REST endpoint, or a
lazy-imported chunk.
Accessibility
Section titled “Accessibility”- The PIN input has
inputMode="numeric",autoComplete="postal-code",maxLength={6}, plusaria-busy(lookup in flight) andaria-invalid(lookup failed / no results) - PIN-lookup status announced via
aria-live="polite" - Each cascade dropdown is a real
<label htmlFor>+<select>pair, fully keyboard navigable and screen-reader announceable - Loading state is non-blocking — user can keep typing
Testing
Section titled “Testing”PIN lookup uses https://api.postalpincode.in. In tests, mock the
lookup prop to return a stub:
import { TkxAddressInput } from 'tekivex-ui';
const stubLookup = async () => [ { Name: 'Bandra', District: 'Mumbai', State: 'Maharashtra', Country: 'India', Pincode: '400050' },];
render(<TkxAddressInput value={{}} onChange={() => {}} lookup={stubLookup} />);For the cascade, the same pattern works — pass a stub divisionsSource:
const stubDivisions = { countries: async () => [{ code: 'IN', name: 'India' }], states: async () => [{ code: 'IN-MH', name: 'Maharashtra' }], districts: async () => [{ code: 'MH-PUN', name: 'Pune' }], subDistricts: async () => [{ code: 'PUN-HAV', name: 'Haveli' }], subDistrictLabel: () => 'Taluka',};
render(<TkxAddressInput value={{}} onChange={() => {}} divisionsSource={stubDivisions} />);See tests/TkxAddressInput.test.tsx for the full coverage — 13 tests
including 7 specifically for the cascade path (countries-cascade,
full-cascade-emit-shape, regional-label-switching, downstream-clear
on upstream pick).
See also
Section titled “See also”- India address form recipe — end-to-end walkthrough with a custom REST loader and PIN + cascade together
tekivex-india-adminon npm — bundled LGD snapshot loader (all 36 states + UTs)TkxPhoneInput— the natural companion field for Indian forms (56 countries, length-based validation)