SearchCommand
A reusable search component with asynchronous search capabilities and keyboard navigation.
Overview
Modern applications often require powerful search interfaces that can handle asynchronous data loading, maintain accessibility standards, and provide an excellent user experience. The SearchCommand component addresses these needs with a reusable, generic search interface.
Built on top of the shadcn/ui command component, SearchCommand adds asynchronous search capabilities, keyboard navigation, loading states, and full TypeScript support for working with any data type.
Basic Search Input
Custom UI Components
SearchCommand Approach
Installation
Install the required shadcn/ui components:
npx shadcn-ui@latest add command popover
Create the SearchCommand component:
'use client'
import * as React from "react"
import { useCallback, useState, useRef } from "react"
import { Check, Loader2 } from 'lucide-react'
import { cn } from "@/lib/utils"
import {
Command,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export interface SearchCommandProps<T> {
onSearch: (value: string) => Promise<T[]>
onItemSelect: (item: T) => void
getItemId: (item: T) => string
getItemLabel: (item: T) => string
placeholder?: string
noResultsText?: string
}
Usage
The SearchCommand component provides a generic interface for implementing async search with any data type. It handles loading states, keyboard navigation, and maintaining input focus.
import { SearchCommand } from '@/components/products/command-search';
interface User {
id: string;
name: string;
email: string;
}
function UserSearch() {
// Function to search users
const searchUsers = async (query: string): Promise<User[]> => {
// In a real app, you would fetch from an API
const response = await fetch(`/api/users/search?q=${query}`);
return response.json();
};
// Handle user selection
const handleUserSelect = (user: User) => {
console.log('Selected user:', user);
// Do something with the selected user
};
return (
<div className="w-full max-w-sm">
<SearchCommand<User>
onSearch={searchUsers}
onItemSelect={handleUserSelect}
getItemId={(user) => user.id}
getItemLabel={(user) => user.name}
placeholder="Search users..."
noResultsText="No users found"
/>
</div>
);
}
API Reference
SearchCommand accepts the following props:
Prop | Type | Description | Required |
---|---|---|---|
onSearch | (value: string) => Promise<T[]> | Async function that returns search results | Yes |
onItemSelect | (item: T) => void | Callback called when an item is selected | Yes |
getItemId | (item: T) => string | Function to get a unique identifier from an item | Yes |
getItemLabel | (item: T) => string | Function to get the display text for an item | Yes |
placeholder | string | Placeholder text for the search input | No |
noResultsText | string | Text to display when no results are found | No |
Examples
Product Search Example
import { SearchCommand } from '@/components/products/command-search';
interface Product {
sku: string;
name: string;
price: number;
category: string;
}
export function ProductSearch() {
// Fetch products from API
const searchProducts = async (query: string): Promise<Product[]> => {
if (!query) return [];
// In a real app, you would make an API call
const response = await fetch(`/api/products/search?q=${query}`);
return response.json();
};
const handleProductSelect = (product: Product) => {
console.log(`Selected: ${product.name} (${product.sku})`);
// Navigate to product page or show product details
window.location.href = `/products/${product.sku}`;
};
return (
<div className="w-full max-w-md mx-auto">
<label htmlFor="product-search" className="text-sm font-medium block mb-2">
Find a product
</label>
<SearchCommand<Product>
onSearch={searchProducts}
onItemSelect={handleProductSelect}
getItemId={(product) => product.sku}
getItemLabel={(product) => `${product.name} - ${product.price.toLocaleString('en-US', { style: 'currency', currency: 'USD' })}`}
placeholder="Search products by name..."
noResultsText="No products found matching your search"
/>
</div>
);
}
Ready to enhance your search experience?
Try SearchCommand today and provide your users with an accessible, powerful search interface.