SearchInput
ImplementedWAI-ARIA combobox with a grouped Top-N typeahead (SEARCH-04, ADR-049). The DS owns debounce, keyboard navigation (ArrowUp/Down, Enter, Escape), aria-activedescendant and the loading/empty/short-query states; the consumer supplies onSearch and receives grouped suggestions. Renders a real GET form, so search still works without JavaScript. ADR-071: the chromeless variant owns its field-level ring (`ring-offset-ep-bg`) as the blessed HeroSearch-style indicator; the boxed (non-chromeless) variant rings the input box itself — never nest either inside a parent focus-within ring.
components/molecules/SearchInput/SearchInput.tsxProps
SearchInput
labelstring—Accessible name and (sr-only) visible label of the combobox.
onSearch(query: string) => Promise<SearchInputGroup[]>—Called after debounce with the trimmed query once it reaches minChars. Groups are { id, label, options: { id, label, href? }[] }.
onSelect(value: string, option: SearchInputOption) => void—Selection callback. The input is set to the option label; the consumer navigates or filters.
onSubmit(query: string) => void—Form submit handler (Enter with no active option). When omitted, the native GET form navigates — the no-JS fallback.
namestring"q"GET parameter name of the fallback form.
actionstring—GET action of the fallback form (defaults to the current URL).
debounceMsnumber300Debounce before onSearch. 0 searches immediately (tests, pre-debounced consumers).
minCharsnumber2Minimum trimmed length before searching; shorter queries show shortQueryLabel.
valuestring—Controlled value. Omit for an uncontrolled input.
onValueChange(value: string) => void—Notified on every keystroke (before debounce).
placeholderstring—Input placeholder (the label remains the accessible name).
classNamestring—Utility class passthrough on the form wrapper (spacing/width only).
idstring—Input id override; the listbox and option ids derive from it.
shortQueryLabelstring"Type at least 2 characters"Copy shown while the query is shorter than minChars.
loadingLabelstring"Searching…"role=status copy while a search is in flight.
noResultsLabelstring"No suggestions"role=status copy when the resolved groups are empty.
renderFormbooleantruefalse renders a <div> instead of the <form role="search"> wrapper so the combobox can live inside a consumer form; Enter with no active option is then left to that outer form, which stays the no-JS GET fallback.
chromelessbooleanfalseRenders the input without its own frame or surface (hero flat field).
leadingIconReact.ReactNode—Icon rendered inside the input's left edge (hero magnifier).
<SearchInput />