Search Input
A search field with debounce, clear button, loading state, keyboard shortcut hint, and animated clear button transition.
Basic
A fully-featured search field with built-in debounce, animated clear button, loading spinner, keyboard shortcut hint, and filled variant.
Installation
Install the component via the CLI in one command.
npx @work-rjkashyap/unified-ui add search-inputpnpm dlx @work-rjkashyap/unified-ui add search-inputnpx @work-rjkashyap/unified-ui add search-inputbunx @work-rjkashyap/unified-ui add search-inputIf you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.
Or install the full package
Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.
npm install @work-rjkashyap/unified-uipnpm add @work-rjkashyap/unified-uiyarn add @work-rjkashyap/unified-uibun add @work-rjkashyap/unified-uiAnatomy
import { SearchInput } from "@work-rjkashyap/unified-ui";Basic Usage
Drop SearchInput anywhere you need a search field. It manages its own internal value by default (uncontrolled).
<SearchInput placeholder="Search..." />Sizes
| Size | Height | Use Case |
|---|---|---|
sm | 32px | Compact toolbars, sidebars |
md | 36px | Default — most contexts |
lg | 40px | Prominent hero-style search |
Variants
Debounced Change
Use onDebouncedChange for expensive operations like API calls. The callback fires after the user stops typing for debounce milliseconds (default 300ms).
<SearchInput
placeholder="Search users..."
debounce={300}
onDebouncedChange={async (query) => {
const results = await searchUsers(query);
setResults(results);
}}
/>Loading State
Show a spinner instead of the search icon while results are loading.
const [loading, setLoading] = useState(false);
const [results, setResults] = useState([]);
<SearchInput
placeholder="Search..."
loading={loading}
onDebouncedChange={async (query) => {
setLoading(true);
const data = await search(query);
setResults(data);
setLoading(false);
}}
/>;Keyboard Shortcut Hint
Pass shortcut to display a keyboard hint badge in the trailing area when the input is empty. The component also registers a global Ctrl+<shortcut> / ⌘+<shortcut> listener that focuses the input.
Clear Button
By default, a clear button appears (with a fade animation) when the input has a value. Set showClear={false} to disable it.
Controlled
Use value and onChange for full programmatic control.
const [query, setQuery] = useState("");
<SearchInput
value={query}
onChange={setQuery}
placeholder="Search..."
onDebouncedChange={fetchResults}
/>;In a Toolbar
A common pattern is to place SearchInput alongside filters in a table toolbar.
<div className="flex items-center gap-2">
<SearchInput
placeholder="Search users..."
className="max-w-xs"
onDebouncedChange={setGlobalFilter}
/>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-36">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
</SelectContent>
</Select>
</div>In a Command Palette Trigger
<button type="button" onClick={() => setOpen(true)} className="w-full">
<SearchInput
placeholder="Search or jump to..."
shortcut="K"
variant="filled"
className="pointer-events-none"
readOnly
/>
</button>Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled value. |
defaultValue | string | "" | Initial value for uncontrolled mode. |
onChange | (value: string) => void | — | Fires on every keystroke with the current value. |
onDebouncedChange | (value: string) => void | — | Fires after the user stops typing for debounce ms. |
debounce | number | 300 | Debounce delay in milliseconds for onDebouncedChange. |
size | "sm" | "md" | "lg" | "md" | Height and font size. |
variant | "default" | "filled" | "default" | Visual variant — bordered or filled/borderless. |
shortcut | string | — | Key to display as hint and register as Ctrl/⌘ + <key> listener. |
showClear | boolean | true | Whether to show the clear button when the input has a value. |
loading | boolean | false | Shows a spinner in place of the search icon. |
placeholder | string | "Search..." | Input placeholder text. |
disabled | boolean | false | Disables the input. |
className | string | — | Additional CSS classes on the wrapper element. |
All standard <input> HTML attributes (except onChange and size) are also forwarded to the underlying input element.
Motion
- Clear button — Fades in and out using the
fadeInFastpreset (opacity 0 → 1, 120ms) viaAnimatePresencewhen the input gains or loses a value. - Animation respects
prefers-reduced-motionviauseReducedMotion().
Accessibility
- Renders a native
<input type="search">element — compatible with all assistive technologies. - The clear button has
aria-label="Clear search". - The loading spinner is
aria-hidden="true". - The shortcut badge is
aria-hiddenand purely decorative; the global keyboard shortcut handles focus programmatically. - Native browser search cancel button is hidden via CSS to avoid duplication with the custom clear button.
disabledprop is forwarded to the underlying input.
Design Tokens
| Token | Usage |
|---|---|
--input | Border color (default variant) |
--muted | Background color (filled variant) |
--ring | Focus ring color |
--radius-md | Wrapper border radius |
--duration-fast | Transition speed |