Packages
@tsproxy/react

@tsproxy/react

Headless React components for search UI with a BaseUI-inspired overrides pattern. Ships unstyled — you control all rendering.

Install

npm install @tsproxy/react @tsproxy/js react-instantsearch

Components

SearchBox

Sub-elements: Root, Form, Input, SubmitButton, ResetButton

<SearchBox
  placeholder="Search..."
  queryHook={debouncedSearch}  // optional: debounce/transform queries
  overrides={{
    Input: { props: { className: "search-input" } },
    SubmitButton: { props: { hidden: true } },
  }}
/>

Hits

Sub-elements: Root, List, Item

<Hits
  hitComponent={({ hit }) => <ProductCard product={hit} />}
  overrides={{
    List: { props: { className: "grid grid-cols-4 gap-4" } },
  }}
/>

RefinementList

Sub-elements: Root, List, Item, Label, Checkbox, LabelText, Count

<RefinementList
  attribute="category"
  overrides={{
    Checkbox: { props: { className: "custom-checkbox" } },
    Count: { props: { className: "text-gray-400 text-xs" } },
  }}
/>

Pagination

Sub-elements: Root, List, Item, Link

<Pagination
  padding={3}
  overrides={{
    Link: { props: { className: "px-3 py-1 rounded hover:bg-gray-100" } },
  }}
/>

Stats

Sub-elements: Root, Text

<Stats
  formatText={(nbHits, ms) => `${nbHits} products (${ms}ms)`}
  overrides={{
    Root: { props: { className: "text-sm text-gray-500" } },
  }}
/>

SortBy

Sub-elements: Root, Select, Option

<SortBy
  items={[
    { value: "products", label: "Relevance" },
    { value: "products/sort/price:asc", label: "Price: Low to High" },
  ]}
/>

NoResults

Sub-elements: Root, Title, Message

<NoResults
  title="Nothing found"
  message="Try different search terms."
/>

HitsSkeleton

Sub-elements: Root, List, Item

<HitsSkeleton
  count={8}
  overrides={{
    List: { props: { className: "grid grid-cols-4 gap-4" } },
  }}
/>

LocaleSelector

Sub-elements: Root, Select, Option

<LocaleSelector
  locales={[
    { code: "en", label: "English" },
    { code: "fr", label: "French" },
  ]}
/>

Overrides System

Every component accepts an overrides prop. Each sub-element can be:

Extended with props:

overrides={{
  Input: { props: { className: "my-class", "aria-label": "Search" } },
}}

Extended with a function (receives default props):

overrides={{
  Item: {
    props: (defaultProps) => ({
      ...defaultProps,
      className: `${defaultProps.className} extra-class`,
    }),
  },
}}

Replaced entirely:

overrides={{
  Checkbox: {
    component: ({ checked, onChange }) => (
      <Switch checked={checked} onChange={onChange} />
    ),
  },
}}

Types

import type {
  Override,
  Overrides,
  SearchBoxProps,
  SearchBoxElements,
  HitsProps,
  HitsElements,
  RefinementListProps,
  RefinementListElements,
  PaginationProps,
  PaginationElements,
  StatsProps,
  StatsElements,
  SortByProps,
  SortByElements,
  NoResultsProps,
  NoResultsElements,
  HitsSkeletonProps,
  HitsSkeletonElements,
} from "@tsproxy/react";