Faceted Search React is a library for building faceted search interfaces in React applications. It provides reusable components and hooks for filtering, sorting, and displaying search results.
Install the package using:
npm install @knaw-huc/faceted-search-react
Build the package using:
npm run build
Run a demo application using:
npm run dev
The demo application showcases the design. By providing the hash #context the components and hooks provided by the
library are showcased.
This library provides a set of reusable basic components for use in a faceted search interface. It also allows setting
up a search implementation using a FacetedSearch context:
import {FacetedSearch} from '@knaw-huc/faceted-search-react';
function App() {
return (
<FacetedSearch facets={facets} searchLabel="Search for" pageSize={pageSize}>
<YourAppComponents/>
</FacetedSearch>
);
}The facets lists all facets with their key, label and a rendering function of the value. The searchLabel provides
the label for the search box. The pageSize is the number of results to show per page. The syncPageToUrl parameter
can be used together with infinite scrolling paging to disable URL sync of the current page.
type Facets = Record<string, Facet>;
interface Facet {
label: string;
valueRenderer?: (value: string, valueLabel?: string) => string;
}
interface SearchState {
query?: string;
facetValues: Record<string, string[]>;
page: number;
sort?: string;
}
interface SearchResults<R> {
items: R[];
total: number;
}Within the FacetedSearch context, you can use the provided hooks to build your search interface. The library also
provides hooked components that bind the basic components with these hooks.
By default, FacetedSearch controls the search state, and syncs it with the URL. To control the state yourself, for example to sync it with your own router, pass state and onStateChange:
<FacetedSearch facets={facets} state={state} onStateChange={setState}>
<YourAppComponents/>
</FacetedSearch>The FacetsSection component is a container for displaying multiple facets in a section. On mobile devices, it hides
the section behind a button.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
children |
ReactNode |
✓ | The content to display inside the facets section. |
The Facet component is a reusable UI component designed to display a collapsible section with a label, optional
informational text, and child content. It supports toggling visibility and provides accessibility features.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
label |
string |
✓ | The label displayed at the top of the facet. | |
infoText |
string |
Optional text providing additional information about the facet. | ||
startOpen |
boolean |
true |
Determines whether the facet starts in an open state. | |
allowToggle |
boolean |
true |
Allows toggling the visibility of the facet content. | |
children |
ReactNode |
✓ | The content to display inside the facet when it is open. |
The SearchFacet component includes a search input field. It allows users to filter search results based on a search
term. It is designed to be used within a FacetsSection. Though not to be used within a Facet component, it can be
used alongside other facets.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
initialQuery |
string |
The initial search query to show in the search input field. | ||
onSearch |
(query: string) => void |
✓ | Callback function to handle search input changes. |
The FilterFacet component is designed to display a list of filter options that users can select to refine their search
results. It supports hierarchical filters. The component is designed to be used within a FacetsSection and a Facet
and can be used alongside other facets. Use the FilterFacetItems component to render the filter options.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
sort |
Sort |
The intitial sorting order. | ||
onTextFilterChange |
(value: string) => void |
Callback function to handle changes in the text filter input. | ||
onSort |
(type: Sort) => void |
Callback function to handle sorting change by the user. |
type Sort = 'asc' | 'desc' | 'hits';The FilterFacetItems component is designed to display a list of filter options that users can select to refine their
search results. It supports hierarchical filters.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
items |
FilterFacetItem[] |
✓ | The facet items to render. | |
selected |
Set<string> |
✓ | An object that keeps track of the selected facet items. | |
maxInitialItems |
number |
The maximum number of items to show initially. | ||
showAmount |
boolean |
true |
Whether to show per item for how many search results it applies. | |
itemsClosed |
boolean |
false |
Whether to hide the child items in a hierarchical structure. | |
onSelect |
(selected: Selected) => void |
✓ | Callback function to handle changes in the selected items. |
interface FilterFacetItem {
itemKey: string;
label: string;
amount: number;
children?: FilterFacetItem[];
}The NumericRangeFacet component is designed to display a range filter that allows users to select a range of numeric
values. The component is designed to be used within a FacetsSection and a Facet and can be used alongside other
facets.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
min |
number |
✓ | The minimum of the allowed range. | |
max |
number |
✓ | The maximum of the allowed range. | |
step |
number |
✓ | The step size for the range. | |
startMin |
number |
min |
The initial minimum value. | |
startMax |
number |
max |
The initial maximum value. | |
onChange |
(min: number, max: number) => void |
✓ | Callback function to handle range changes. |
The DateRangeFacet component is designed to display a range filter that allows users to select a range of date values.
The component is designed to be used within a FacetsSection and a Facet and can be used alongside other facets.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
min |
string (ISO 8601) |
✓ | The minimum date of the allowed range. | |
max |
string (ISO 8601) |
✓ | The maximum date of the allowed range. | |
startMin |
string (ISO 8601) |
min |
The initial minimum date value. | |
startMax |
string (ISO 8601) |
max |
The initial maximum date value. | |
onChange |
(min: string, max: string) => void |
✓ | Callback function to handle range changes. |
The SelectedFacets component displays the currently selected facets and allows users to clear them.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
selectedFacets |
SelectedFacet[] |
✓ | A list of selected facets to display. | |
onClear |
() => void |
✓ | Callback function to clear selected facets. |
interface SelectedFacet {
facet?: string;
value: string;
onRemove: () => void;
}The Pagination component is designed to display pagination controls for navigating through search results.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
currentPage |
number |
✓ | The current page number. | |
pages |
{ [page: number]: string } |
✓ | The pages to render and the links. | |
prev |
string |
The link to the previous page. | ||
next |
string |
The link to the next page. |
The ResultsView is a container for search results. It will show a loading state if the results are still being loaded.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
children |
ReactNode |
✓ | The content to display inside the results view. |
The ResultCard container is designed to hold a single search result card. It will show a loading state if the results
are still being loaded. It is used within the ResultsView to display individual search results in a card format.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
children |
ReactNode |
✓ | The content to display inside the result card. |
The ResultCardBasic component is a basic implementation of a search result card. It is designed to be used within the
ResultCard and displays the title, description and the tags.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
title |
string |
✓ | The title of this search result. | |
link |
string |
✓ | The link to the detail page of this search result. | |
description |
string |
✓ | The description of this search result. | |
tags |
string[] |
The tags for this search result. | ||
startMarker |
string |
If the description is highlighted, the start marker of the highlighted parts. |
||
endMarker |
string |
If the description is highlighted, the end marker of the highlighted parts. |
The ResultCardSubResults component is designed to display sub-results within a search result card. It is designed to
be used within the ResultCard.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
title |
string |
✓ | The title of this search result. | |
link |
string |
✓ | The link to the detail page of this search result. | |
items |
ResultCardSubResultsItem[] |
✓ | A list with the sub results to render. | |
maxInitialItemsShown |
number |
The maximum number of sub-results to show initially. |
interface ResultCardSubResultsItem {
columns: string[];
mainColumnIndex: number;
onClick?: () => void;
}The Highlight component is designed to parse text with rendered highlighted segments.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
text |
string |
✓ | The text with highlighted segments. | |
startMarker |
string |
✓ | The start marker of the highlighted parts. | |
endMarker |
string |
✓ | The end marker of the highlighted parts. | |
render |
(text: string, index: number) => ReactNode |
✓ | A render function for a highlighted part. |
The useSearchState hook is used to obtain the current search state SearchState.
interface SearchState {
facetValues: Record<string, string[]>;
page: number;
sort?: string;
}The useFacet hook is used to register and manage the state of a specific facet.
| Parameter | Value type | Description |
|---|---|---|
facetKey |
string |
The key of the facet to manage. |
defaultValue |
string | string[] |
The default value(s) for this facet. |
The hook returns an object with the values:
label:string: The human-readable label for the facet.values:string | string[]: The current value (s) for this facet.setValues:(value: string | string[]) => void: A function to set the value (s) for this facet.
The useFacets hook is used to work with the state of all facets in the search interface. It returns an object with the
values:
facets:Record<string, Facet>: An object containing all registered facets, where the keys are the facet keys and the values are objects with the facet's label and a function to get a human-readable value for a given value.facetValues:Record<string, string[]>: An object containing the current values for all facets, where the keys are the facet keys and the values are arrays of selected values.valueLabels:Record<string, Record<string, string>>: An object containing the labels for values of all facets, where the keys are the facet keys and the values are a mapping for a value to a label.addFacetValue:(facetKey: string, value: string) => void: A function to add a value for a specific facet.removeFacetValue:(facetKey: string, value: string) => void: A function to remove a value for a specific facet.clearFacets:() => void: A function to clear all (selected/active) facets.
interface Facet {
label: string;
getReadable: ((value: string) => string) | null;
}The useSearchFacet hook is used to register and manage the state of a search facet. It provides functionality to fetch
search results based on the current search state and the search term.
| Parameter | Value type | Description |
|---|---|---|
facetKey |
string |
The key of the search facet to manage. |
label |
string |
The human readable label for the search facet. |
The hook returns an object useSearchFacetReturn with the current search term and a function to change the search term.
interface useSearchFacetReturn {
query: string;
onSearch: (query: string) => void;
}The useFilterFacet hook is used to register and manage the state of a filter facet. The facet items are managed by
another hook useFilterFacetSelection.
| Parameter | Value type | Description |
|---|---|---|
facetKey |
string |
The key of the filter facet to manage. |
The hook returns an object UseFilterFacetReturn with the text filter and sort selection and functions to interact with
the filter facet.
interface UseFilterFacetReturn {
label: string;
textFilter: string;
sort: Sort;
onTextFilterChange: (textFilter: string) => void;
onSort: (sort: Sort) => void;
}
type Sort = 'asc' | 'desc' | 'hits';The useFilterFacet hook is used to register and manage the state of a filter facet items.
| Parameter | Value type | Description |
|---|---|---|
facetKey |
string |
The key of the filter facet to manage. |
The hook returns an object UseFilterFacetSelectionReturn with the items selected and a function to interact with new
selections.
interface UseFilterFacetSelectionReturn {
selected: Set<string>;
onSelect: (selected: Set<string>) => void;
}The useNumericRangeFacet hook is used to register and manage the state of a numeric range facet. It provides
functionality to set and get the current numeric range values.
| Parameter | Value type | Description |
|---|---|---|
facetKey |
string |
The key of the range facet to manage. |
min |
number |
The minimum value of the range. |
max |
number |
The maximum value of the range. |
The hook returns an object useRangeFacetReturn with the current range and a function to change the range.
interface useRangeFacetReturn {
label: string;
value?: [number, number];
onChange: (min: number, max: number) => void;
}The useDateRangeFacet hook is used to register and manage the state of a numeric range facet. It provides
functionality to set and get the current numeric range values.
| Parameter | Value type | Description |
|---|---|---|
facetKey |
string |
The key of the range facet to manage. |
min |
string (ISO 8601) |
The minimum date value of the range. |
max |
string (ISO 8601) |
The maximum date value of the range. |
The hook returns an object useDateRangeFacetReturn with the current range and a function to change the range.
interface useDateRangeFacetReturn {
label: string;
value?: [string, string];
onChange: (min: string, max: string) => void;
}The useSelectedFacets hook is used to fetch all the selected facets. It returns a list of selected facets of type
SelectedFacet and a function to clear all selected facets.
interface SelectedFacet {
itemKey: string;
name?: string;
label: ReactNode;
onRemove: () => void;
}The useUpdateTotal hook publishes the total number of search results into the store, so components that do not fetch
the results themselves, such as the pagination, still know how many there are.
The usePagination hook is used to manage the pagination state of the search results. It returns a Pagination object
with the current page, page size, total number of results, and functions to navigate through the pages.
interface Pagination {
page: number;
pageSize: number;
total: number;
setPage: (page: number) => void;
getPrevPages: (max: number) => number[];
getNextPages: (totalPages: number, max: number) => number[];
}The useHighlight hook is used to parse text with rendered highlighted segments, indicated by a startMarker and an
endMarker. It returns the normalized text with the offsets of each highlighted segment.
interface HighlightedText {
text: string;
spans: Offsets[];
}
interface Offsets {
start: number;
end: number;
}The HookedSearchFacet component is a wrapper around the SearchFacet component that uses the useSearchFacet hook to
manage the search state. It provides a search input field that allows users to filter search results based on a search
term.
The HookedFilterFacet component is a wrapper around the FilterFacet component that uses the useFilterFacet hook to
manage the filter state and the useFilterFacetSelection hook to manage the item selection. It renders the facet header
and the filter and sort toolbar, and provides the Suspense boundary. Supply the useItems hook the component can use
to fetch and update the facet items.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
facetKey |
string |
✓ | The key of the filter facet to manage. | |
useItems |
UseFilterFacetItems |
✓ | Hook returning the items for the given state. | |
infoText |
string |
Optional text providing additional information about the facet. | ||
allowFilter |
boolean |
true |
Whether to allow filtering of the items. | |
allowSort |
boolean |
true |
Whether to allow sorting of the items. | |
allowToggle |
boolean |
true |
Allows toggling the visibility of the facet content. | |
startOpen |
boolean |
true |
Determines whether the facet starts in an open state. | |
showAmount |
boolean |
true |
Whether to show per item for how many search results it applies. | |
itemsClosed |
boolean |
false |
Whether to hide the child items in a hierarchical structure. | |
maxInitialItems |
number |
The maximum number of items to show initially. |
type UseFilterFacetItems = (state: FilterFacetState) => FilterFacetItem[];The HookedNumericRangeFacet component is a wrapper around the NumericRangeFacet component that uses the
useNumericRangeFacet hook to manage the numeric range state. It provides a range input that allows users to select a
range of numeric values.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
facetKey |
string |
✓ | The key of the range facet to manage. | |
infoText |
string |
Optional text providing additional information about the facet. | ||
min |
number |
✓ | The minimum of the allowed range. | |
max |
number |
✓ | The maximum of the allowed range. | |
step |
number |
✓ | The step size for the range. | |
startMin |
number |
min |
The initial minimum value. | |
startMax |
number |
max |
The initial maximum value. | |
allowToggle |
boolean |
true |
Allows toggling the visibility of the facet content. | |
startOpen |
boolean |
true |
Determines whether the facet starts in an open state. |
The HookedDateRangeFacet component is a wrapper around the DateRangeFacet component that uses the
useDateRangeFacet hook to manage the date range state. It provides a range input that allows users to select a range
of dates.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
facetKey |
string |
✓ | The key of the range facet to manage. | |
infoText |
string |
Optional text providing additional information about the facet. | ||
min |
string (ISO 8601) |
✓ | The minimum date of the allowed range. | |
max |
string (ISO 8601) |
✓ | The maximum date of the allowed range. | |
startMin |
string (ISO 8601) |
min |
The initial minimum date value. | |
startMax |
string (ISO 8601) |
max |
The initial maximum date value. | |
allowToggle |
boolean |
true |
Allows toggling the visibility of the facet content. | |
startOpen |
boolean |
true |
Determines whether the facet starts in an open state. |
The HookedSelectedFacets component is a wrapper around the SelectedFacets component that uses the useFacets hook
and the useSelectedFacets hook to manage the selected facets. It displays the currently selected facets and allows
users to clear them. You can pass an includeQuery prop to indicate if the search query should be shown as well, which
defaults to true.
The HookedPagination component is a wrapper around the Pagination component that uses the usePagination hook to
manage the pagination state and to read the total search result size. It provides pagination controls for navigating
through search results.
The HookedResultsView component is a wrapper around the ResultsView component. It provides the Suspense boundary
for the results and publishes the total through useUpdateTotal. Supply the useResults hook the component can use to
fetch the results.
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
useResults |
(state: ResultsState) => SearchResults<C> |
✓ | Hook returning the results for the given state. | |
id |
(result: C) => Key |
✓ | A function to determine the key for the given result. | |
children |
(result: C) => ReactNode |
✓ | A render function for the given result. |
The HookedInfiniteResultsView component is an infinite paging alternative to HookedResultsView.
The library includes built-in support for internationalization. By default, all UI strings are in English, but you can customize them to support any language.
The FacetedSearch component accepts three i18n-related props:
| Parameter | Value type | Required? | Default value | Description |
|---|---|---|---|---|
translate |
TranslateFn |
Custom translate function from your i18n library. Takes precedence if provided. | ||
translations |
Record<string, string> |
Object to override specific translation keys. Merged with defaults. | ||
locale |
string / Intl.Locale |
'en' | Locale string or Intl.Locale object. |
type TranslateFn = (key: string, options?: Record<string, unknown>) => string;If you're using an i18n library like react-intl or i18next, you can pass your own translate function:
With react-intl:
import {useIntl} from 'react-intl';
function App() {
const intl = useIntl();
const translate = (key: string, options?: Record<string, unknown>) =>
intl.formatMessage({id: key}, options);
const locale = myLocale();
return (
<FacetedSearch facets={facets} translate={translate} locale={locale}>
<YourAppComponents/>
</FacetedSearch>
);
}With i18next:
import {useTranslation} from 'react-i18next';
function App() {
const {t} = useTranslation();
const locale = myLocale();
return (
<FacetedSearch facets={facets} translate={t} locale={locale}>
<YourAppComponents/>
</FacetedSearch>
);
}For simple customizations without an external i18n library, you can override specific keys:
const myTranslations = {
'search.label': 'Zoek naar tekst',
'pagination.previous': 'Vorige',
'pagination.next': 'Volgende',
};
function App() {
return (
<FacetedSearch facets={facets} translations={myTranslations}>
<YourAppComponents/>
</FacetedSearch>
);
}The following translation keys are available for customization:
| Key | Default Value | Used In |
|---|---|---|
search.label |
Search for text | SearchFacet |
search.button.aria |
Search | SearchFacet |
filter.label |
Filter on facet items | FilterFacet |
filter.placeholder |
Type to filter | FilterFacet |
filter.sort.asc |
Order from A to Z | FilterFacet |
filter.sort.desc |
Order from Z to A | FilterFacet |
filter.sort.hits |
Order by the amount of results | FilterFacet |
filter.amount.aria |
Amount of results | FilterFacet |
filter.showAll |
All items | FilterFacet |
facet.aria |
Facet for {{label}} | Facet |
facet.info.aria |
Click for a description about the facet | Facet |
facet.toggle.open |
Click to open the facet | Facet |
facet.toggle.close |
Click to close the facet | Facet |
facet.skip |
Skip {{label}} and go to next facet | Facet |
facets.toggle |
Search filters | FacetsSection |
pagination.previous |
Previous | Pagination |
pagination.next |
Next | Pagination |
selected.aria |
Selected filters | SelectedFacets |
selected.label |
Selected filters: | SelectedFacets |
selected.remove.aria |
Click to remove from search filters | SelectedFacets |
selected.clear |
Clear filters | SelectedFacets |
range.min |
Min | RangeFacet |
range.max |
Max | RangeFacet |
results.seeMore |
See {{count}} more reactions | ResultCardSubResults |
Some translation keys support interpolation using the {{variable}} syntax:
facet.ariaandfacet.skipuse{{label}}for the facet labelresults.seeMoreuses{{count}}for the number of additional items
When using a custom translate function, ensure it handles these interpolation placeholders appropriately.
When no i18n configuration is provided:
- All strings default to English
- The library works out of the box without any setup required