A search field that does the whole job: `onSearch` fires once typing pauses (and at once on Enter), a shortcut shown as key caps focuses it from anywhere on the page (Command K on a Mac, Control K elsewhere), a spinner takes the search icon's place while results load, and a clear button appears once there is a query. With a `storageKey` it remembers recent searches and offers them in a list while the field is empty, each one removable, with a Clear all; the list is a proper combobox with arrow keys, Enter and Delete. Escape closes the list, then clears the query, then lets go of the field. Built on the shadcn Input.
The import and the props worth knowing about, in one place.
import { FieldSearch } from "@/components/beste/component/field-search";
const [loading, setLoading] = useState(false);
<FieldSearch
placeholder="Search songs and venues"
storageKey="recent-searches" // remembers searches on this device
debounce={300} // ms of quiet before onSearch
loading={loading}
onSearch={async (query) => {
setLoading(true);
console.log("Search", query);
setLoading(false);
}}
/>
// No shortcut, no memory, just a field
<FieldSearch shortcut={false} tone="muted" size="sm" onSearch={(query) => console.log(query)} />Every one of these is in the component already. They are listed because a props table cannot mention a gesture, so nothing else on this page can tell you they exist.
| Command K / Control K | Focus the field from anywhere on the page. |
|---|---|
| Enter | Search at once and remember the query, or pick the highlighted recent search. |
| Arrow down / Arrow up | Move through the recent searches while the field is empty. |
| Delete | Remove the highlighted recent search. |
| Escape | Close the list, then clear the query, then leave the field. |
Read from the component's own type, so this cannot drift from what it accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | The query, controlled. |
defaultValue | string | "" | |
onValueChange | (value: string) => void | — | Every keystroke. |
onSearch | (query: string) => void | — | The query once typing pauses for `debounce` ms, and at once on Enter or a picked recent search. |
debounce | number | — | Milliseconds of quiet before `onSearch`. @defaultValue 250 |
loading | boolean | — | Shows a spinner in place of the search icon. |
shortcut | string | false | — | Focuses the field from anywhere on the page; `false` turns it off. @defaultValue "mod+k" |
storageKey | string | — | Remembers searches under this localStorage key and offers them while the field is empty. |
defaultRecent | string[] | — | Recent searches used until the reader has any of their own. |
maxRecent | number | 5 | |
placeholder | string | "Search" | |
aria-label | string | "Search" | |
labels | Partial<typeof DEFAULT_LABELS> | — | Words used by the component, for translation. |
name | string | — | |
disabled | boolean | — | |
tone | "muted" | "outline" | "ghost" | "outline" | |
size | "sm" | "default" | "lg" | "default" | |
className | string | — |