Search input
A search field with a clear button, a global keyboard shortcut, a loading state and recent searches while it is empty.
npx prodular-ui add search-inputRecent searches
Focus the empty field to see them. Arrow keys move, Enter picks, Escape clears.
Press / anywhere to focus. Recent searches show while the field is empty.
"use client"
import * as React from "react"
import { SearchInput } from "@/components/ui/search-input"
const initial = ["Kestrel Health overdue", "INV-2087", "Payouts over $10k", "Northwind Logistics"]
export default function SearchInputRecent() { const [query, setQuery] = React.useState("") const [recent, setRecent] = React.useState(initial) const [last, setLast] = React.useState<string | null>(null)
return ( <div className="flex w-full max-w-sm flex-col gap-2"> <SearchInput value={query} onValueChange={setQuery} onSearch={(q) => { if (!q.trim()) return setLast(q) setRecent((r) => [q, ...r.filter((x) => x !== q)].slice(0, 5)) }} recent={recent} onClearRecent={() => setRecent([])} shortcut="/" placeholder="Search invoices, customers and payouts" aria-label="Search" /> <p className="text-[13px] text-muted-foreground"> {last ? `Showing results for ${last}.` : "Press / anywhere to focus. Recent searches show while the field is empty."} </p> </div> )}Search as you type
The icon becomes a spinner while results load. Cmd or Ctrl and K focuses it.
- Kestrel Health
- Halden Health
"use client"
import * as React from "react"
import { SearchInput } from "@/components/ui/search-input"
const customers = ["Northwind Logistics", "Kestrel Health", "Brightline Studio", "Meridian Freight", "Lumen Analytics", "Halden Health"]
export default function SearchInputLoading() { const [query, setQuery] = React.useState("health") const [loading, setLoading] = React.useState(false) const [results, setResults] = React.useState(customers.filter((c) => c.toLowerCase().includes("health")))
// A fake request: results arrive 450ms after the last keystroke. React.useEffect(() => { const t = setTimeout(() => { setResults(customers.filter((c) => c.toLowerCase().includes(query.trim().toLowerCase()))) setLoading(false) }, 450) return () => clearTimeout(t) }, [query])
return ( <div className="flex w-full max-w-sm flex-col gap-3"> <SearchInput value={query} onValueChange={(q) => { setQuery(q) setLoading(true) }} loading={loading} shortcut="mod+k" placeholder="Find a customer" aria-label="Find a customer" /> <ul className="divide-y rounded-lg border bg-card text-sm shadow-xs"> {results.length ? ( results.map((c) => ( <li key={c} className="px-3 py-2"> {c} </li> )) ) : ( <li className="px-3 py-2 text-muted-foreground">No customer matches {query}.</li> )} </ul> </div> )}Anatomy
<SearchInput aria-label="Search invoices" value={query} onValueChange={setQuery} onSearch={run} shortcut="/" recent={recent}/>Guidance
Use it when
- Searching a list, a table or the whole product.
- shortcut on the one main search of a page, so keyboard users can reach it from anywhere.
- recent when people repeat searches, like customer names or invoice numbers.
Avoid
- Two inputs on one page with the same shortcut.
- Picking a value for a form. Use Combobox, which keeps the choice.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Give it aria-label (or a visible label) that says what is searched, for example Search invoices.
- Use onValueChange for search as you type, debounced, and set loading while the request is in flight.
- Use onSearch for searches that run on Enter, and add the term to recent there.
- Keep recent to five or fewer items, newest first, and offer onClearRecent.
API
SearchInput
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue / onValueChange | string | Controlled or uncontrolled text. | |
| onSearch | (value) => void | Enter, or picking a recent search. | |
| shortcut | "/" | "mod+k" | Focuses the field from anywhere. Shows a key hint while empty. | |
| recent / onClearRecent | string[] / () => void | Recent searches shown while focused and empty. | |
| loading | boolean | false | Shows a spinner in place of the search icon. |
| size | "sm" | "default" | "lg" | "default" | Height. Matches Input sizes. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add search-input# or, with the registry in components.jsonnpx shadcn@latest add @prodular/search-input