KQ-1667 · ADDRESS/CASCADE
Address Cascade
An address form under a search line: the search is a combobox that looks addresses up as you type, shows a quiet spinner only when a lookup is slow and always ends with Enter it manually. Choosing a suggestion closes the list on the exit ease and fills the fields one after another from top to bottom, staggerSpeed apart, each value sliding down into its field on springs.snap while an accent wash fades behind it and a check pops on snap and goes. Arrows move through the list, Enter picks, Escape closes then clears, and a failed lookup falls back to manual entry with what was typed in the first line.
Delivery address
Search once; the fields fill themselves.
delivery · type a street or postcode
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/address-cascade
Props
- fields
- CascadeField[]
- default defaultCascadeFields
- The fields under the search in reading order: { id, label, autoComplete?, span?: "full" | "half", optional?, placeholder? }.
- suggestions
- CascadeSuggestion[]
- default defaultCascadeSuggestions
- The addresses the built-in lookup searches: { id, label, detail, values } with a value per field id.
- onSuggest
- (query: string) => CascadeSuggestion[] | Promise<CascadeSuggestion[]>
- default —
- Looks addresses up for a query; a promise shows the spinner once slow, a rejection falls back to manual entry. Defaults to filterCascadeSuggestions over suggestions.
- value / defaultValue
- Record<string, string>
- default — / {}
- Field values by field id, controlled or initial.
- onValueChange
- (values: Record<string, string>) => void
- default —
- Fires with every value after an edit, a pick or a manual fallback.
- onChange
- (fieldId: string, value: string, values: Record<string, string>) => void
- default —
- Fires for each field that changed, with its new value and every value.
- onPick
- (suggestion: CascadeSuggestion) => void
- default —
- A suggestion was chosen from the list.
- onSubmit
- (values: Record<string, string>) => void | Promise<unknown>
- default —
- The form was submitted with every required field filled; a promise shows Saving…, a rejection says Not saved.
- lookupDelay
- number
- default 0
- Pretend latency for the built-in lookup, ms.
- spinnerDelay
- number
- default 250
- A lookup slower than this shows the spinner, ms; faster ones never flash it.
- minQuery
- number
- default 2
- Characters typed before a lookup runs.
- fieldStyle
- "boxed" | "line" | "filled"
- default "boxed"
- Field look: bordered boxes, an underline, or tinted wells.
- columns
- number
- default 2
- Columns at wide widths, 1 to 3; under about 30rem the form always takes one.
- staggerSpeed
- number
- default 70
- Time between one field filling and the next in the cascade, ms.
- accent
- string
- default "var(--accent-bright)"
- The landing wash, the check, the spinner and Save: any CSS colour.
- title
- string
- default "Delivery address"
- The heading and the form's accessible name.
- submitLabel
- string
- default "Save address"
- The submit button's words.
- maxHeight
- number
- default 560
- Tallest the form grows before it scrolls inside, px.
- disabled
- boolean
- default false
- Fields and search are read-only and Save does nothing; the form dims.
- className
- string
- default —
- Classes for the outer form.
| Prop | Type | Default | Description |
|---|---|---|---|
| fields | CascadeField[] | defaultCascadeFields | The fields under the search in reading order: { id, label, autoComplete?, span?: "full" | "half", optional?, placeholder? }. |
| suggestions | CascadeSuggestion[] | defaultCascadeSuggestions | The addresses the built-in lookup searches: { id, label, detail, values } with a value per field id. |
| onSuggest | (query: string) => CascadeSuggestion[] | Promise<CascadeSuggestion[]> | — | Looks addresses up for a query; a promise shows the spinner once slow, a rejection falls back to manual entry. Defaults to filterCascadeSuggestions over suggestions. |
| value / defaultValue | Record<string, string> | — / {} | Field values by field id, controlled or initial. |
| onValueChange | (values: Record<string, string>) => void | — | Fires with every value after an edit, a pick or a manual fallback. |
| onChange | (fieldId: string, value: string, values: Record<string, string>) => void | — | Fires for each field that changed, with its new value and every value. |
| onPick | (suggestion: CascadeSuggestion) => void | — | A suggestion was chosen from the list. |
| onSubmit | (values: Record<string, string>) => void | Promise<unknown> | — | The form was submitted with every required field filled; a promise shows Saving…, a rejection says Not saved. |
| lookupDelay | number | 0 | Pretend latency for the built-in lookup, ms. |
| spinnerDelay | number | 250 | A lookup slower than this shows the spinner, ms; faster ones never flash it. |
| minQuery | number | 2 | Characters typed before a lookup runs. |
| fieldStyle | "boxed" | "line" | "filled" | "boxed" | Field look: bordered boxes, an underline, or tinted wells. |
| columns | number | 2 | Columns at wide widths, 1 to 3; under about 30rem the form always takes one. |
| staggerSpeed | number | 70 | Time between one field filling and the next in the cascade, ms. |
| accent | string | "var(--accent-bright)" | The landing wash, the check, the spinner and Save: any CSS colour. |
| title | string | "Delivery address" | The heading and the form's accessible name. |
| submitLabel | string | "Save address" | The submit button's words. |
| maxHeight | number | 560 | Tallest the form grows before it scrolls inside, px. |
| disabled | boolean | false | Fields and search are read-only and Save does nothing; the form dims. |
| className | string | — | Classes for the outer form. |