Found a typo?
Instantsearchjs instantsearch.js

Instant Search Results Page - instantsearch.js

Introduction

Instantsearch resultpage instantsearchjs demo

In this tutorial, you will use Algolia to create an instant search results page where the whole page (results, filters and pagination) gets updated as you type, to provide end-users with a lightning fast search experience.

We’ll take the example of an e-commerce website and implement the search on the front-end using Algolia’s instantsearch.js UI library.

If you haven’t done so yet, now is the right time to read our How it Works guide. It gives you the big picture about Algolia and the role played by the back-end and the front-end in our approach to search.

With this tutorial, we want to:

  • Display results as you type,
  • Update filters and pagination as you type,
  • Provide 3 types of filters: regular facets, disjunctive facets (with search capabilities) and numeric filtering,
  • Add a “sort by” menu to have multiple sort criteria,
  • Display a “clear search” button when there are no results.

This tutorial has been built using the Best Buy Developer API. It’s based on 10,000 products extracted from their API.

Important Updates

Dec. 2016: React InstantSearch

React lovers, capitalizing on the success of instantsearch.js, we’ve built a new brand-new version of it called React InstantSearch, so each of the widget is exposed as a React component.

Don’t wait a second, and switch to the React InstantSearch version of this guide.

Importing your Data

Algolia is a SaaS solution, which means you need to index your search-related data on Algolia’s servers for the search to work.

For this tutorial, we’ll create an index named instant_search and populate it with our 10,000 records. To create this index, go to your dashboard in the indices section and click the “New Index” button at the top right corner of the page.

Instant search 2

Name your index “instant_search” and validate

Once the index is created, you can either upload the 10,000 records without writing a single line of code, or using the API via an API client in your favorite language.

Import using the dashboard

Download this JSON file and use our web interface to do so.

Instant search 3

Click “upload file” to send the JSON file to your index

Import using the API

If you want to import your own data, you should have a look at the import guide.

Here’s what our records look like:

[
  {
    "objectID": 3325038,
    "name": "Apple - iPad mini 3 Wi-Fi 16GB - Gold",
    "description": "The most advanced iPad mini is loaded with innovations like the Touch ID fingerprint sensor and Retina display.",
    "brand": "Apple",
    "categories": ["Computers & Tablets", "Tablets", "All Tablets"],
    "hierarchicalCategories": {
      "lvl0": "Computers & Tablets",
      "lvl1": "Computers & Tablets > Tablets",
      "lvl2": "Computers & Tablets > Tablets > iPad"
    },
    "type": "Apple wifi",
    "price": 399.99,
    "price_range": "200 - 500",
    "image": "http://img.bbystatic.com/BestBuy_US/images/products/3325/3325038_sb.jpg",
    "url": "http://www.bestbuy.com/site/apple-ipad-mini-3-wi-fi-16gb-gold/3325038.p?id=1219090454897&skuId=3325038&cmp=RMX&ky=1uWSHMdQqBeVJB9cXgEke60s5EjfS6M1W",
    "popularity": 8593
  },
  // other products [...]
]

As you can see, our records have 4 types of information:

  • Attributes you want to search in (name, description, brand, …)
  • Numerical values we can use to specify our ranking (popularity)
  • Attributes you want to filter on (price, categories, …)
  • Other attributes used to display the results (image)

Index configuration (using the Dashboard)

Now that we’ve imported all our data into our index, we can move to the configuration part of it. That step is key as it allows us to tell the engine how we’d like results to be sorted for a given search, but also to define the attributes we’d like to use as filters in our search ui.

Relevance

There are several configuration options you can act on to tune your overall index relevancy. The most important ones are the searchable attributes and the attributes reflecting record popularity (the “custom ranking”).

Thus, our first step into building a great search page is to identify what will be made searchable, and how records will be ranked within a result set.

Searchable Attributes

The searchableAttributes parameter (formerly known as attributesToIndex) controls which attributes should be searchable. It is important to choose those wisely and to exclude any attribute whose only purpose is for displaying, filtering, or ranking.

For example, consider a link to an image: you want to store it and retrieve it for each result but it doesn’t make sense to have it be part of the searchable attributes. Doing so would alter your overall relevance by including irrelevant words into your search index.

Be aware that the order of the attributes in your searchableAttributes parameter is very important. Our engine uses this order to decide how to rank two results for which a keyword is found in different attributes.

For instance, with searchableAttributes=['name', 'description'], if we receive the query “iPad”, products that have “iPad” in their name attribute will be ranked higher than those having the word “iPad” only in their description attribute.  

In this tutorial, we’ll define the following order of importance for our attributes:

  1. brand
  2. name
  3. categories
  4. description (unordered)

By default, a keyword matched at the beginning of an attribute is considered more important. You can disable this behavior by setting the unordered flag, like we did for the description attribute:

Instant search 4

Set searchableAttributes in the “Ranking” tab of your index

Custom Ranking

Algolia’s default ranking formula includes a customRanking criterion which allows you to add business metrics to the relevance calculation.

Which information should you use for the customRanking? Very easy: think about how you would want all your records ranked when the search box is empty. Amazon would probably want to show the products with the highest number of sales first, LinkedIn might want to show the people with the most connections, and Yelp could define a combination of the ratings and the number of reviews of each of their businesses.

In our case, we have a popularity attribute representing the number of units sold. You can use any numerical value that ranks the products: number of sales, number of views or number of likes, or even a score that you calculated before indexing.

By adding such attribute to the customRanking setting, search results will be built and sorted by combining the text-relevance criteria with the custom ranking to return the most relevant results.

Instant search 5

Add popularity to your custom ranking in the Ranking tab of your index

Note that depending on your metrics, you might want to change the sorting direction from DESC to ASC by clicking the button on the right of the attributes list.

Be aware that your attributes used in customRanking have to have numeric values. String values will get sorted alphabetically, which will not work well with numbers!

Other settings

There are many other settings that you can use to configure your index to your needs.

  • You can use ignorePlurals to consider plural forms of words equivalent to the singular form (for example car/cars will be considered as equal), thus making records contain plural forms as textually relevant as those containing singular forms.
  • Synonyms to consider some sets of words as equal at query time (eg. “red” and “vermilion”).
  • Use removeWordsIfNoResults to alter the query when no results are found. You can gradually remove words from the query until some results are found, or make all words optional in the query.

These are just a few examples. You can either:

Filters and facets

In the UI we’re building we’d like our users to not only find products performing a full-text search, but also giving them the possibility to refine the result set using filters on the brand name, the category, the price, … When building the search UI, we will see that it exists different types of filters (aka facets) that can be used according to how we want the users to interact with. But for now let’s just define the list of attributes we want our users to be able to filter on.

Instant search 10

Setting attributesForFaceting in the Display tab of the main index

Note: there’s no indication as to how those facets will be used: in a menu, or refinementList, or numericRefinementList, … That part is covered later in the tutorial, when implementing the front-end code.

Multiple Sort Criteria (optional)

By default, the results ranking will be based on textual relevance and on the customRanking value we’ve just set. To give more freedom to the user, we’d like to let them sort by price (ascending or descending) too, or by any other numerical value (timestamp, nb likes, …). This is doable by having different ranking strategies for your data.

However, to achieve the best performance possible, the engine pre-computes the results ranking at indexing-time. This is an optimization to ensure you will always have outstanding performance at query time. The consequence of this approach is that you cannot change the sort criteria of an index at query time: each index has a unique ranking strategy.

Using replicas

The replicas setting is used to create replicas of a primary index. The content of those replicas will automatically be synchronized with their primary index, but they can have different configurations.

That’s how we will achieve the sort by feature: we will create multiple replicas with the same content, but with different ranking strategies. We’ll then target a different replica when sorting by relevance (main index), price descending (instant_search_price_desc replica), or price ascending (instant_search_price_asc replica).

So let’s create two replicas for this index that will take care of the price sorting:

Instant search 6

Setting replicas in the Replicas tab of your index

Note: there’s no mandatory naming pattern for replicas. However as a convention, replicas used for sorting are named using this pattern: <primary_index_name>_<sorting_attribute_name>_<direction>

When the replicas are created, they inherit the configuration settings of their primary index. We can then change those settings individually in each replicas configuration to change the ranking strategy (or other settings, depending on your use case).

To sort results by a specific numerical attribute, you just need to add this attribute at the top of your ranking formula and choose the correct sorting direction.

Go to your instant_search_price_asc index by selecting it in the top-left index selector in your Algolia Dashboard, and scroll down to the bottom of the Ranking tab. There you can switch ON the Sort by Attribute setting, and then add the price attribute to the field:

Instant search 7

Change the sorting direction to ASC by clicking the button on the right

You can then do the same for the instant_search_price_desc index, keeping the default DESC direction this time:

Instant search 8

String attributes cannot be added to the Ranking Formula criteria. To sort using a string attribute (to sort by name for instance), move the customRanking criterion at the top of your Ranking Formula and add your string attribute to the customRanking list.

Once that is done, set the typo-tolerance of the replica index to typo=min. This will keep the feature on, but only display results with the lowest count of typos: either 0 or 1.

Instant search 9

That’s it! We now have all the required indices to implement our sort by selector. We’ll get there soon.

Index configuration (using the API)

The entire configuration we have set using the Dashboard can, of course, be done programmatically via our API. It can prove useful to have a script ready to set an index configuration, if you automate your production processes for instance.

Here is the code equivalent to the configuration we have done manually above:

require 'algoliasearch'
Algolia.init("application_id" => "YourApplicationID", "api_key" => "YourAPIKey")
# Create instant_search index and set its settings
index = Algolia::Index.new('instant_search')
settingsTask = index.set_settings({
  "searchableAttributes" => ["brand", "name", "categories", "unordered(description)"],
  "customRanking" => ["desc(popularity)"],
  "replicas" => ["instant_search_price_asc", "instant_search_price_desc"],
  "attributesForFaceting" => ["brand", "type", "categories", "price"]
})
# Wait for the task to be completed (to make sure replica indices are ready)
index.wait_task(settingsTask['taskID'])
# Configure the replica indices
Algolia::Index.new('instant_search_price_asc').set_settings({
  "ranking" => ["asc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom"]
})
Algolia::Index.new('instant_search_price_desc').set_settings({
  "ranking" => ["desc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom"]
})
<?php

// Composer autoload -- assumes you have installed the 'algolia/algoliasearch-client-php' Composer package
// if you are not using composer: require_once 'path/to/algoliasearch.php';
require __DIR__ . '/vendor/autoload.php';
$client = new \AlgoliaSearch\Client("YourApplicationID", "YourAPIKey");
// Create instant_search index and set its settings
$index = $client->initIndex("instant_search");
$settingsTask = $index->setSettings(array(
  "searchableAttributes" => array("brand", "name", "categories", "unordered(description)"),
  "customRanking" => array("desc(popularity)"),
  "replicas" => array("instant_search_price_asc", "instant_search_price_desc"),
  "attributesForFaceting" => array("brand", "type", "categories", "price")
));
// Wait for the task to be completed (to make sure replica indices are ready)
$index->waitTask(settingsTask["taskID"]);
// Configure the replica indices
$client->initIndex("instant_search_price_asc")->setSettings(array(
  "ranking" => array("asc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom")
))
$client->initIndex("instant_search_price_desc")->setSettings(array(
  "ranking" => array("desc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom")
));

var algoliasearch = require('algoliasearch');
var client = algoliasearch('YourApplicationID', 'YourAPIKey');
// Create instant_search index and set its settings
var index = client.initIndex('instant_search');
index.setSettings({
  searchableAttributes: ['brand', 'name', 'categories', 'unordered(description)'],
  customRanking: ['desc(popularity)'],
  replicas: ['instant_search_price_asc', 'instant_search_price_desc'],
  attributesForFaceting: ['brand', 'type', 'categories', 'price']
}, function(err, content) {
  if(err)
  {
    throw err;
  }
  // Wait for the setSettings task to finish (to make sure replica indices are ready)
  index.waitTask(content.taskID, function() {
    // Configure the replica indices
    client.initIndex('instant_search_price_asc').setSettings({
      ranking: ["asc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom"]
    });
    client.initIndex('instant_search_price_desc').setSettings({
      ranking: ["desc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom"]
    });
  });
});

from algoliasearch import algoliasearch
client = algoliasearch.Client("YourApplicationID", 'YourAPIKey')
# Create instant_search index and set its settings
index = client.init_index('instant_search')
settingsTask = index.set_settings({
  "searchableAttributes": ["brand", "name", "categories", "unordered(description)"],
  "customRanking": ["desc(popularity)"],
  "replicas": ["instant_search_price_asc", "instant_search_price_desc"],
  "attributesForFaceting":["brand", "type", "categories", "price"]
})
# Wait for the task to be completed (to make sure replica indices are ready)
index.wait_task(settingsTask['taskID'])
# Configure the replica indices
client.init_index('instant_search_price_asc').set_settings({
  "ranking": ["asc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom"]
})
client.init_index('instant_search_price_desc').set_settings({
  "ranking": ["desc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom"]
})
APIClient client = new ApacheAPIClientBuilder("YourApplicationID", "YourAPIKey");
// Create instant_search index and set its settings
Index<Product> index = client.initIndex("instant_search", Product.class);
index
  .setSettings(new IndexSettings()
    .setSearchableAttributes(Arrays.asList("brand", "name", "categories", "unordered(description)"))
    .setCustomRanking(Arrays.asList("desc(popularity)"))
    .setReplicas(Arrays.asList("instant_search_price_asc", "instant_search_price_desc"))
    .setAttributesForFaceting(Arrays.asList("brand", "type", "categories", "price"))
  )
  .waitForCompletion(); //Wait for the task to be completed (to make sure replica indices are ready)
//Configure the replica indices
client
  .initIndex("instant_search_price_asc")
  .setSettings(new IndexSettings()
    .setRanking(Arrays.asList("asc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom"))
  );
client
  .initIndex("instant_search_price_desc")
  .setSettings(new IndexSettings()
    .setRanking(Arrays.asList("desc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom"))
  );
//For the DSL
import algolia.AlgoliaDsl._
//For basic Future support, you might want to change this by your own ExecutionContext
import scala.concurrent.ExecutionContext.Implicits.global
val client = new AlgoliaClient("YourApplicationID", "YourAPIKey")
// Create instant_search index and set its settings
client.execute {
  changeSettings of "instant_search" `with` IndexSettings(
    searchableAttributes = Some(Seq("brand", "name", "categories", "unordered(description)")),
    customRanking = Some(Seq(CustomRanking.desc("popularity"))),
    replicas = Some(Seq("instant_search_price_asc", "instant_search_price_desc")),
    attributesForFaceting = Some(Seq("brand", "type", "categories", "price"))
  )
}
//Configure the replica indices
client.execute {
  changeSettings of "instant_search_price_asc" `with` IndexSettings(
    ranking = Some(Seq(Ranking.asc("price"), Ranking.typo, Ranking.geo, Ranking.words, Ranking.proximity, Ranking.attribute, Ranking.exact, Ranking.custom))
  )
client.execute {
  changeSettings of "instant_search_price_desc" `with` IndexSettings(
    ranking = Some(Seq(Ranking.desc("price"), Ranking.typo, Ranking.geo, Ranking.words, Ranking.proximity, Ranking.attribute, Ranking.exact, Ranking.custom))
  )
}
client := algoliasearch.NewClient("YourApplicationID", "YourAPIKey")
index := client.InitIndex("instant_search")
res, err := index.SetSettings(algoliasearch.Map{
  "searchableAttributes":     []string{"brand", "name", "categories", "unordered(description)"},
  "customRanking":         []string{"desc(popularity)"},
  "replicas":                []string{"instant_search_price_asc", "instant_search_price_desc"},
  "attributesForFaceting": []string{"brand", "type", "categories", "price"},
})
if err != nil {
  fmt.Println("Cannot set index settings")
  return
}
if err = index.WaitTask(res.TaskID); err != nil {
  fmt.Println("Task not published")
  return
}
res, err = client.InitIndex("instant_search_price_asc").SetSettings(algoliasearch.Map{
  "ranking": []string{"asc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom"},
})
res, err = client.InitIndex("instant_search_price_desc").SetSettings(algoliasearch.Map{
  "ranking": []string{"desc(price)", "typo", "geo", "words", "proximity", "attribute", "exact", "custom"},
})

Layout

For building the UI, we’ll use our instantsearch.js library. This library is a collection of widgets specifically designed to create the kind of instant search experience we are building here. Each widget handles a part of the UI (search box, hits, facets, pagination, …) and is kept in sync with the other widgets automatically.

As we can see on the diagram below, instantsearch.js takes care of the communication with the Algolia API, so you don’t have to worry about it, and instead have the opportunity to focus on the experience you want to build configuring and styling each of the widget.

Instant search 27

Once added to your javascript and properly configured, each widget injects rendered HTML into the layout elements you declare in their configuration parameters. In our case we’d like the searchBox widget to put its content into the <div id="search-input"> element. Of course, to fit the best with everyone’s UI, each widget allows to pass a custom HTML template and custom cssClasses which can both then be styled with some CSS.

Dependencies

At the top of the page, let’s add the necessary CSS files:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/instantsearch.js/1/instantsearch.min.css">
<link rel="stylesheet" type="text/css" href="style.css">

Here we’re adding the default instantsearch.js styles, and our own CSS file to customize the widgets to our liking.

Let’s do the same with the required JavaScript files. Place them just before the closing tag of body:

<script src="https://cdn.jsdelivr.net/instantsearch.js/1/instantsearch.min.js"></script>
<script src="app.js"></script>

Out of the box, instantsearch.js’s CSS file only contains the bare minimum rules to make it usable, but has no strong styling. To reproduce the tutorial theme, copy the content of the CSS file to your style.css, and put the associated images along in an img/ folder.

Page layout

As mentioned, using instantsearch.js is as easy as creating some container elements in your HTML and telling the lib to initialize the different widgets in each of them.

Our first step will then be to create the HTML layout that will contain our widgets.

Header section

The header will contain our demo logo and the search box:

<body>
  <header>
    <a href="." title="Home"><img src="img/instant_search_logo@2x.png"/></a>
    <div id="search-input"></div>
    <div id="search-input-icon"></div>
  </header>

Main section

The main section of the page will contain the search results and the filtering/facets widgets, in a typical two-column layout. It goes right below the header:

<main>
  <div id="left-column">
    <div id="category" class="facet"></div>
    <div id="brand" class="facet"></div>
    <div id="price" class="facet"></div>
    <div id="type" class="facet"></div>
  </div>
  <div id="right-column">
    <div id="sort-by-wrapper"><span id="sort-by"></span></div>
    <div id="stats"></div>
    <div id="hits"></div>
    <div id="pagination"></div>
  </div>
</main>

The left column will host our facets: categories, brands, price and product types. The right column will display the search results (called hits), a “sort by” menu and pagination links.

<HTML /> templates

By default, each widget comes with its default html templates.

The template technology we have chosen is hogan.js, which is a clone of mustache.js.

We will see in the next section that most the widgets of can get passed a custom html template, allowing you to display their content using your look’n’feel.

Adding UI Widgets

Now that we have a seen how instantsearch.js work, and prepared the page layout, let’s add widgets and bring our search page to life!

You will need:

  • Your search credentials: the Application ID and Search only API Key that you can get from the credentials page in your Algolia Dashboard
  • The name of the index you want to query

There are 3 simple steps to initialize an instantsearch.js app:

  1. Instantiate instantsearch.js
  2. Add and configure the widgets you need
  3. Start instantsearch.js

Creating an instantsearch.js instance

var search = instantsearch({
  // Replace with your own values
  appId: 'latency',
  apiKey: '6be0576ff61c053d5f9a3225e2a90f76', // search only API key, no ADMIN key
  indexName: 'instant_search',
  urlSync: true
});

Along with providing our Algolia credentials, we also activate the urlSync option. It will keep the browser url in sync and allow our users to copy paste urls corresponding to the current search state.

Adding widgets

As mentioned earlier the instantsearch.js library comes with a collection of widgets (see all) that are designed to work together in an instant search results page. Each widget you need must be created and added to your instantsearch.js instance by using two methods:

  1. The instantsearch.widgets.nameOfWidget method to effectively create the widget
  2. The search.addWidget method of your instantsearch.js instance, to make this widget known by the synchronization mechanisms

Here’s how we create a searchBox and add it to instantsearch.js:

search.addWidget(
  instantsearch.widgets.searchBox({
    container: '#search-input',
    placeholder: 'Search for products'
  })
);

Each widget has a container option that defines where in your page the widget should be attached. Here we provide a CSS Selector but it can also be a reference to a DOMElement.

Instant search 11

searchBox widget

Pro-tip: Using an explicit placeholder in the input guides users in their search.

hits

Next widgets to add is the one responsible for the display of the search results: the hits widget.

search.addWidget(
  instantsearch.widgets.hits({
    container: '#hits',
    hitsPerPage: 10,
    templates: {
      item: getTemplate('hit'),
      empty: getTemplate('no-results')
    }
  })
);

We see here that up to 10 search results will be rendered inside the <div id="hits">.

In addition we’ve declared two custom HTML templates that will allow us to customize the display of each result, but also in the case no result has been found.

Note: getTemplate is a small utility to get the underlying template. Here’s the function to copy at the bottom of your file:

function getTemplate(templateName) {
  return document.getElementById(templateName + '-template').innerHTML;
}
item templates

The item template will be called for each hit, using the hit’s data as the rendering context. Which means you have direct access to the different attributes that are indexed in your Algolia index.

You can also access some special attributes like _higlightResult that gives you access to the same attributes with HTML highlights on the matched texts within them.

In this template we’re using specific attributes of the products objects and displaying them with highlighting whenever it makes sense - so users can easily understand displayed results.

<script type="text/html" id="hit-template">
  <div class="hit">
    <div class="hit-image">
      <img src="{{image}}" alt="{{name}}">
    </div>
    <div class="hit-content">
      <h3 class="hit-price">${{price}}</h3>
      <h2 class="hit-name">{{{_highlightResult.name.value}}}</h2>
      <p class="hit-description">{{{_highlightResult.description.value}}}</p>
    </div>
  </div>
</script>

By default the API return the highlighted part between <em> tags (like <em>smart</em>phone}) so you can easily style them. You must disable escaping on highlighted values, which can be easily done using three curly-braces {{{html}}}.

Note that our template is wrapped in a <script type="text/html"> tag, so that we can “store” some HTML code without rendering it on the page.

empty templates

In case of no result, the following template will show an informative message, repeating the search query that failed to return any hit.

<script type="text/html" id="no-results-template">
  <div id="no-results-message">
    <p>We didn't find any results for the search <em>"{{query}}"</em>.</p>
    <a href="." class="clear-all">Clear search</a>
  </div>
</script>

To clear the search, we’re using a link that points to the current page without any parameter, effectively clearing the search.

stats

In addition to the search results, we’d like to display the number of those using the stats widgets:

search.addWidget(
  instantsearch.widgets.stats({
    container: '#stats'
  })
);

multiple sort options

Next up is the sort menu widget:

search.addWidget(
  instantsearch.widgets.sortBySelector({
    container: '#sort-by',
    autoHideContainer: true,
    indices: [{
      name: search.indexName, label: 'Most relevant'
    }, {
      name: search.indexName + '_price_asc', label: 'Lowest price'
    }, {
      name: search.indexName + '_price_desc', label: 'Highest price'
    }]
  })
);

About the options:

  • autoHideContainer: true is used to automatically hide the widget when there are no results to display
  • indices is an the array of indices we want to use for sorting when the corresponding label is clicked in the select box

When the stats, hits and sortBySelector widgets are added, we can see this:

Instant search 12

stats, hits and sortBySelector widgets in action

pagination

Now let’s add a pagination widget:

search.addWidget(
  instantsearch.widgets.pagination({
    container: '#pagination'
  })
);

Instant search 13

pagination widget

Filters: refinementList, rangeSlider, menu

That’s already a good instant search page. We now need to add the filtering widgets. Remember, we’ve added 4 attributes to the list of attributesForFaceting: brand, categories, type and price.

Looking at the Filtering & Faceting guide we identified that we want to use 3 types of filtering widgets:

refinementList

Used for brands and categories, that widget displays the list of facet values available for each of them, and let the user select one or multiple of those values at a time. Meaning that users will be able to select multiple brands and/or categories in order to narrow down the set of results.

Note: as the number of available brands can be very large, we’ve enabled the searchForFacetValues option on refinementList widget. That way users are will be able to search for all the brands available in the current result set.

rangeSlider

Used on the price, the widget allows to specify a numerical range. This is commonly used for price filtering.

Used on the type, that widget provides a list of facet values where only one value can be selected at a time. Like a radio button, when a new value is selected, the previous one is deselected. It is used here to allow the user to display only products of a specific type.

Instantsearch resultpage common filteringwidgets

Widgets overview

The code below shows the configuration for each of the widget type used.

// Filter on categories
search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#category',
    attributeName: 'categories',
    limit: 10,
    sortBy: ['isRefined', 'count:desc', 'name:asc'],
    operator: 'or',
    templates: {
      header: '<h5>Category</h5>'
    }
  })
);

// Filter on brand names
// Include the possibility to search for facet values
search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#brand',
    attributeName: 'brand',
    limit: 10,
    sortBy: ['isRefined', 'count:desc', 'name:asc'],
    operator: 'or',
    templates: {
      header: '<h5>Brand</h5>'
    },
    searchForFacetValues: {
      placeholder: 'Search for brands',
      templates: {
        noResults: '<div class="sffv_no-results">No matching brands.</div>'
      }
    }
  })
);
// Filter on the price
search.addWidget(
  instantsearch.widgets.rangeSlider({
    container: '#price',
    attributeName: 'price',
    templates: {
      header: '<h5>Price</h5>'
    }
  })
);
// Filter on the product type
search.addWidget(
  instantsearch.widgets.menu({
    container: '#type',
    attributeName: 'type',
    limit: 10,
    sortBy: ['isRefined', 'count:desc', 'name:asc'],
    templates: {
      header: '<h5>Type</h5>'
    }
  })
);
Other available widgets for filtering

In addition to the 3 filtering widget used here, instantsearch.js includes other ones like

  • priceRange: often used in addition to the rangeSlider one
  • hierachicalMenu: similar to the menu widget, but facet values a displayed in multiple levels.

To learn more about those other filtering widgets, and customization options all of them offer, feel free to look at our Filtering & Faceting guide once that one completed.

Now that we’ve added all our widgets, let’s start the whole interface:

search.start();

See it live

Open the index.html file in your favorite web browser and play with your new instant search results page.

Instant search 15

The instantsearch.js library has many more widgets, all described in the Filtering & Faceting guide.

Did you find this page helpful?

We're always looking for advice to help improve our documentation! Please let us know what's working (or what's not!) - we're constantly iterating thanks to the feedback we receive.

Send us your suggestions!