Unified UI

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-input
pnpm dlx @work-rjkashyap/unified-ui add search-input
npx @work-rjkashyap/unified-ui add search-input
bunx @work-rjkashyap/unified-ui add search-input

If 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-ui
pnpm add @work-rjkashyap/unified-ui
yarn add @work-rjkashyap/unified-ui
bun add @work-rjkashyap/unified-ui

Anatomy

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

SizeHeightUse Case
sm32pxCompact toolbars, sidebars
md36pxDefault — most contexts
lg40pxProminent 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

PropTypeDefaultDescription
valuestringControlled value.
defaultValuestring""Initial value for uncontrolled mode.
onChange(value: string) => voidFires on every keystroke with the current value.
onDebouncedChange(value: string) => voidFires after the user stops typing for debounce ms.
debouncenumber300Debounce delay in milliseconds for onDebouncedChange.
size"sm" | "md" | "lg""md"Height and font size.
variant"default" | "filled""default"Visual variant — bordered or filled/borderless.
shortcutstringKey to display as hint and register as Ctrl/⌘ + <key> listener.
showClearbooleantrueWhether to show the clear button when the input has a value.
loadingbooleanfalseShows a spinner in place of the search icon.
placeholderstring"Search..."Input placeholder text.
disabledbooleanfalseDisables the input.
classNamestringAdditional 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 fadeInFast preset (opacity 0 → 1, 120ms) via AnimatePresence when the input gains or loses a value.
  • Animation respects prefers-reduced-motion via useReducedMotion().

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-hidden and 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.
  • disabled prop is forwarded to the underlying input.

Design Tokens

TokenUsage
--inputBorder color (default variant)
--mutedBackground color (filled variant)
--ringFocus ring color
--radius-mdWrapper border radius
--duration-fastTransition speed

On this page