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

terminal
npm install @headband/react

Optional 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.

tsx
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>
  );
}
ParameterTypeDefaultDescription
clientHeadbandClientRequiredClient created with headband(host, apiKey).
indexstringRequiredThe index UID to search.
initialQuerystring""Optional initial search query.
childrenReactNodeRequiredChild 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 | null
useRefinementList(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/off
usePagination()

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 internally
useStats()

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 relevance
useRange({ 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 filter
useHighlight({ 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.

tsx
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>
  );
}