React SDK
@headband/react provides InstantSearch-inspired hooks and pre-built components for building search UIs with React. It handles debouncing, state management, facet filtering, pagination, and more out of the box.
Installation
npm install @headband/reactOptional CLI setup for browser-safe React search
Run npx @headband/cli init to generate a config and starter component. The generated config reads from NEXT_PUBLIC_HEADBAND_KEY, so the CLI only accepts scoped hb_src_ search keys. Keep hb_adm_ admin keys server-side for indexing, settings, and sync jobs.
HeadbandProvider
Wrap your search UI in a HeadbandProvider. It creates a client, manages search state, and provides context to all child hooks and components.
import { headband, HeadbandProvider } from "@headband/react";
const client = headband("https://your-instance.com", "hb_src_YOUR_SEARCH_KEY");
function App() {
return (
<HeadbandProvider client={client} index="products">
{/* Search components go here */}
</HeadbandProvider>
);
}| Parameter | Type | Default | Description |
|---|---|---|---|
client | HeadbandClient | Required | Client created with headband(host, apiKey). |
index | string | Required | The index UID to search. |
initialQuery | string | "" | Optional initial search query. |
children | ReactNode | Required | Child components that use Headband hooks. |
Hooks
useSearch()Core hook exposing the full search state and all actions. Use the specialized hooks below for most cases.
const { query, results, isLoading, error, setQuery, setPage, refresh } = useSearch();useSearchBox()Binds to the search input. Returns the current query plus setters.
const { query, setQuery, clear } = useSearchBox();
// query: string - Current query
// setQuery(q: string) - Update query (resets pagination, triggers debounced search)
// clear() - Reset query to ""useHits<T>()Returns the current search hits with loading and error state. Supports generics for typed hits.
const { hits, isLoading, error } = useHits<Product>();
// hits: Hit<T>[] - Each hit has document fields + _formatted + __data
// isLoading: boolean
// error: Error | nulluseRefinementList(props)Provides facet filtering for a given attribute. Automatically registers the facet with the provider.
const { items, refine, canToggle, isLoading } = useRefinementList({
attribute: "category",
limit: 20, // max facet values (default: 20)
sortBy: "count", // "count" or "alpha" (default: "count")
});
// items: { value: string, count: number, isRefined: boolean }[]
// refine(value: string) - Toggle a facet value on/offusePagination()Pagination state derived from the current search results.
const { currentPage, totalPages, totalHits, setPage, canPrevious, canNext, pages } = usePagination();
// pages: number[] - Window of page numbers for rendering a page list
// Pages are 0-indexed internallyuseStats()Returns summary stats about the current search results.
const { totalHits, processingTimeMs, query, isLoading } = useStats();useSortBy({ items })Sort-by dropdown state. Pass sort options, get back the current selection.
const { currentSort, setSort, items } = useSortBy({
items: [
{ value: "price:asc", label: "Price (low to high)" },
{ value: "price:desc", label: "Price (high to low)" },
],
});
// items[n].isSelected: boolean - Added to each item
// setSort(null) - Reset to default relevanceuseRange({ attribute })Numeric range filter for a given attribute.
const { min, max, stats, setRange, clear, isLoading } = useRange({ attribute: "price" });
// stats: { min: number, max: number } | undefined - Overall min/max from engine
// setRange({ min: 10, max: 500 }) - Set range filter
// clear() - Remove range filteruseHighlight({ hit, attribute })Extracts the highlighted value for a specific attribute from a hit. Sanitizes HTML -- only allows <em> tags.
const { value, highlighted } = useHighlight({ hit, attribute: "title" });
// value: string - Safe HTML string with <em> tags
// highlighted: boolean - Whether the value contains highlight marks
// Usage: <span dangerouslySetInnerHTML={{ __html: value }} />Components
Pre-built UI components with default Tailwind styles. Set styled=false for unstyled variants.
<SearchBox />Search input with built-in clear button and keyboard handling.
Props: placeholder, className, autoFocus, onQueryChange, styled
<Hits />Renders search result hits. Pass a custom hitComponent for custom rendering.
Props: hitComponent, className, emptyComponent, styled
<RefinementList />Checkbox list for facet filtering on a given attribute.
Props: attribute, limit, sortBy, className, styled
<Pagination />Page navigation with previous/next buttons and page number list.
Props: className, styled
<Stats />Displays result count and processing time (e.g. "42 results in 2ms").
Props: className, styled
<SortBy />Dropdown to switch sort order.
Props: items, className, styled
<Highlight />Renders highlighted text for a given hit attribute.
Props: hit, attribute, className
<RangeInput />Min/max numeric input fields for range filtering.
Props: attribute, className, styled
<PoweredBy />"Powered by Headband" attribution badge.
Props: className, styled
Full Example
A complete product search page with facets, sorting, pagination, and highlighting.
import {
headband,
HeadbandProvider,
SearchBox,
Hits,
RefinementList,
Pagination,
Stats,
SortBy,
Highlight,
} from "@headband/react";
const client = headband(
"https://your-instance.com",
"hb_src_YOUR_SEARCH_KEY"
);
function ProductHit({ hit }) {
return (
<div className="p-4 border rounded-lg">
<Highlight hit={hit} attribute="title" />
<p className="text-sm text-gray-500">${hit.price}</p>
</div>
);
}
export default function SearchPage() {
return (
<HeadbandProvider client={client} index="products">
<div className="max-w-5xl mx-auto p-6">
<SearchBox placeholder="Search products..." autoFocus />
<div className="flex gap-8 mt-6">
<aside className="w-48 shrink-0">
<h3 className="text-sm font-semibold mb-2">Category</h3>
<RefinementList attribute="category" />
<h3 className="text-sm font-semibold mt-4 mb-2">Sort</h3>
<SortBy
items={[
{ value: "price:asc", label: "Price: Low to High" },
{ value: "price:desc", label: "Price: High to Low" },
]}
/>
</aside>
<main className="flex-1">
<Stats />
<Hits hitComponent={ProductHit} />
<Pagination />
</main>
</div>
</div>
</HeadbandProvider>
);
}