Facet dropdown with InstantSearch.js
All Search and Discovery user interfaces display, in addition to a search box, a series of filters that allow end users to narrow down their search. As explained in the docs, these onscreen filters are called facets. A typical facet is an attribute like “brand” or “price”, and facet values are the individual brands and prices. By clicking on a facet value, users can include and exclude whole categories of products. For example, by selecting “Blue” in the “Color” facet filter, a user can exclude every product except blue ones.
For a long time, ecommerce websites have been displaying facets on the left side of the screen, allowing easy access. But this placement reduces the space available to display products. With the rise of mobile-first design and touchscreen, online businesses have started to display facets at the top of their product listing.
That’s the case of Lacoste which increased its sales by +150% sales with Algolia’s search.
Dropdown faceting offers two benefits:
- It increases facet visibility and accessibility, encouraging more usage.
- It simplifies the screen, leaving more room for products and creating more on-screen breathing space, an important UX design choice.
This guide shows how to turn a facet filter, commonly represented by a refinementList
or a hierarchicalMenu
, widget, into a custom widget with a drop-down menu layout.
The provided code uses InstantSearch.js, which can be taken as-is or as a reference if you want to replicate the same pattern using React, Vue, or Angular InstantSearch.
Wrapper elements
By design, each drop-down menu refinement widget wrapper has two elements:
- A button with a label and facet count
- A drop-down menu box
Both can be customized using settings. Each of the dropdowns come wrapped in its own <div />
, which allows multiple facets in the same container, thus creating a horizontal list.
To ease the creation of a drop-down menu widget, this guide provides a factory wrapper function called createDropdown()
that takes two parameters:
- The refinement widget you want to turn into a drop-down menu
- An object for optional settings
This function, located in the src/Dropdown.js
file, returns an InstantSearch widget that can be placed alongside other widgets on your search screen.
All examples in this guide assume you’ve included InstantSearch.js in your web page from a CDN. If, instead, you’re using it with a package manager, adjust how you import InstantSearch.js and its widgets for more information.
1
2
3
4
const myFacetDropdown = createDropdown(
instantsearch.widgets.refinementList,
{ /* optional settings */ }
});
Quick start
First, you create your own drop-down menu widget. Later, you customize it.
To create the drop-down menu, use a refinementList
widget on your type
facet.
1 - Prepare the HTML markup
- Import all JavaScript files, such as Algolia search, InstantSearch.js, and your own JS files.
- Declare a
<div id="type">
placeholder for the drop-down menu widget for thetype
facet.
In index.html
:
1
2
3
4
5
6
7
8
9
10
11
12
<div class="container">
<div id="searchbox"></div>
<!-- Container where will be added all Dropdown facet filters -->
<div class="search-panel__filters">
....
<div id="type"></div>
....
</div>
<div id="hits"></div>
</div>
2 - Import Dropdown.js
and create the widget drop-down menu
Import the createDropdown()
function from the src/dropdown.js
file before initializing an InstantSearch instance with your Algolia credentials and create the Dropdown widget.
In this case, use a refinementList
widget for the type facet. As a result, you create a customized refinementListDropdown
widget for the type
facet that can also be re-used for other facets.
In src/app.js
:
1
2
3
4
5
6
7
8
// Import of the Dropdown.js file
import { createDropdown } from './Dropdown'
// Creation of the refinementListDropdown widget
const refinementListDropdown = createDropdown(
instantsearch.widgets.refinementList,
{ closeOnChange: true, }
)
Here, instantsearch.widgets.refinementList
refers to InstantSearch’s refinementList
widget. See the widget reference guide.
3 - Add your created widget to the InstantSearch instance
Now that you’ve created your refinementListDropdown
widget, you’re ready to add it to your InstantSearch instance.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Initialization of InstantSearch and our widgets
search.addWidgets([
instantsearch.widgets.searchBox({
container: '#searchbox',
}),
instantsearch.widgets.hits({...}),
// Adding the refinementListDropdown widget on the `type` facet
refinementListDropdown({
container: '#type', // the CSS Selector of the DOM element inside which the widget is inserted.
attribute: 'type', // the name of the attribute in the records.
searchable: true // whether to add a search input to let the user search for more facet values.
}),
...
]);
search.start();
By design, the createDropdown()
function creates a widget that acts as a wrapper around the initial widget passed in as a parameter. For that reason, the created refinementListDropdown
widget can take any of the settings available in the refinementList
widget. For example, { limit: 5, showMore: true }
limits the default number of displayed facet values to “5”.
Settings
As mentioned, the createDropdown()
function takes two parameters:
- A mandatory refinement widget to turn into a drop-down menu
- An object with optional settings
1
2
3
4
5
6
7
8
9
createDropdown(
baseWidget,
{
cssClasses: userCssClasses = {},
buttonText,
buttonClassName,
closeOnChange,
} = {}
) {
Here are several optional settings passed to the function using the second parameter, a typical JavaScript object.
cssClasses
(Object - Optional)
This option expects an object that lets you override the CSS class in your code. Here’s an example with default values:
1
2
3
4
5
6
cssClasses = {
root: 'ais-Dropdown',
button: 'ais-Dropdown-button'
buttonRefined: 'ais-Dropdown-button--refined'
closeButton: 'ais-Dropdown-close'
}
buttonText
(String | Function - Optional)
This is the text displayed in the DropDown button. It can be a string or a function. By default, it shows the price of the active price refinement. Here’s an example from the priceMenuDropdown
widget.
1
2
3
4
5
6
buttonText({ items }) {
const refinedItem = (items || []).find(
(item) => item.label !== 'All' && item.isRefined
);
return refinedItem ? `Price (${refinedItem.label})` : 'Price Menu';
},
buttonClassName
(String - Optional)
Same as buttonText
, but for the button CSS class name. Here’s an example from the priceMenuDropdown
widget.
1
2
3
4
5
6
buttonClassName({ items }) {
const isRefined = (items || []).find(
(item) => item.label !== 'All' && item.isRefined
);
return isRefined && 'ais-Dropdown-button--refined';
},
closeOnChange
(Boolean | Function - Optional)
This argument can be a boolean or a function that returns true
if you want the drop-down menu to close as soon as a user selects a value. If it’s false
, it doesn’t close automatically, thus enabling the end user to select more than one facet value. Here’s an example in which the code returns true
if the end user is using a mobile device.
1
2
3
const MOBILE_WIDTH = 375;
/* ... */
closeOnChange: () => window.innerWidth >= MOBILE_WIDTH
Customizing the UI
The generated markup lets you customize the look and feel to your needs, to change the default aspect of the drop-down menu with a few lines of CSS. Here’s the default version:
You can customize two aspects to meet to your needs:
-
Inline facet values
The
brandDropdown
widget uses an inline list. Code is available in theapp.css
file.Here’s the CSS:
Copy1 2 3 4 5
.my-BrandDropdown .ais-RefinementList-list { width: 20rem; display: flex; flex-wrap: wrap; }
-
Fixed height drop-down menu
The
categoriesDropdown
widget, which is hierarchical, uses a fixed height list. Code is available in theapp.css
file.Here’s the CSS:
Copy1 2 3 4
#category .ais-HierarchicalMenu { height: 195px; overflow: auto; }
Providing mobile support
On mobile devices, more than anywhere else, displaying facet refinements over the results is a must-have, considering the limited real estate. Dropdown widgets are a great fit, but you need to tweak the display so they can take advantage of the screen’s width and height.
The mobile version uses the following CSS media query:
1
2
3
@media only screen and (max-width: 375px) {
...;
} // see the full code
It also uses the following JavaScript settings at each drop-down menu widget level:
1
2
3
4
5
6
const MOBILE_WIDTH = 375
const refinementListDropdown = createDropdown(
instantsearch.widgets.refinementList,
{ closeOnChange: () => window.innerWidth >= MOBILE_WIDTH, }
)
This demo gets you started by providing a basic look and feel for mobile devices. You might want to refactor the provided code to match your current design and thus offer the best experience to your mobile users.