Guides / Solutions / Ecommerce / Search / Autocomplete

Predictive search suggestions

Predictive search suggestions lets users quickly access the content they’re looking for because the most relevant suggestions appear directly inside the search box. They can also see the most popular searches and discover more of your content.

Before you begin

This guide requires InstantSearch.js and a Query Suggestions index. If you already have one, go directly to the detailed implementation overview.

Implementation guide

Creating a Query Suggestions index

  1. Go to the Query suggestions tab of the dashboard.
  2. Click the New Query Suggestion button.
  3. Fill in the relevant “Name”, “Languages”, and “Source Index” inputs, then click the Accept button.

For more details, check out the Query Suggestions guide.

High-level overview

To create predictive search suggestions, you overlap two containers (a search box and a predictive box), then send your user’s current query to your suggestions index at every keystroke.

Based on the query, the predictive box displays and consistently updates the first result from the suggestions. You can also display other results under the search box (in a drop-down menu or a grid).

  1. Wrap your search input in a container (for example, a <div>).
  2. Declare a second container within the first one. This second container displays the suggestions.
  3. Use CSS to adjust the position of the search box so that it perfectly overlaps with the suggestions container.
  4. Change the text color of the suggestion container so that the query and the recommendation are distinctive.
  5. At every keystroke, send the query to your Query Suggestions index.
  6. Every time you get a response from the Query Suggestions index, update the text in your suggestions container with the first result that starts with the same letters as the input query. You can also display other suggestions beneath your search box if you want.

Predicted suggestions must begin with the same letters as your user's input.

Detailed overview

This guide walks you through creating a predictive search box widget, which you can import into your app.

First, write a render function that creates a search box with three <div>s:

  • The search box,
  • The overlapping predictive box
  • A display area for extra suggestions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const renderSearchBoxContainer = (placeholder, value) => {
  return `
      <div id="searchbox">
        <div id="predictive-box" style="display: none;">
          <span id="predictive-box-text"></span>
        </div>
        <div class="search-box-container">
          <input autocapitalize="off"
            placeholder="${placeholder}"
            id="search-box-input"
            autocomplete="off"
            autocorrect="off"
            role="textbox"
            spellcheck="false"
            type="search"
            value="${value}">
        </div>
        <div id="clear-input"><i class="fas fa-times"></i></div>
        <fieldset id="suggestion-tags-container"></fieldset>
      </div>
    `;
};

The search box is composed of three main parts. This guide refers to them as:

  • The search box: the input field where users type in their search queries (.search-box-container).
  • The predictive box: the <div> that overlaps with the search box and contains the top suggestion (.predictive-box).
  • The suggestions tags: the <div> that contains all the extra suggestions from the Query Suggestions index (.suggestion-tags-container).

Then, write the CSS to style the search box and predictive box. The goal is to ensure:

  • The <div>s overlap perfectly
  • The predictive box appears behind the search box (so that your users can still type their queries)
  • The predictive box and search box have different text colors.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.search-box-container input {
  border: none;
  border-bottom: 1px solid #cecece;
  width: 100%;
  font-size: 20px;
  background: transparent;
  padding: 0 !important;
  outline: none;
  height: 100%;
  position: relative;
  z-index: 99;
  font-family: Arial, Helvetica, sans-serif;
}

#predictive-box {
  height: 49px;
  padding: 0 !important;
  display: flex;
  align-items: center;
  position: absolute;
  border-bottom: 1px solid transparent;
}

#predictive-box-text {
  font-family: Arial, Helvetica, sans-serif;
  font-size: 20px;
  color: #a2a2a2;
}

This guide doesn’t cover the complete CSS, but you can find it in the source code.

Define a helper function that checks if users have pressed a specific key.

1
const isKey = (event, code, name) => event.which === code || event.keyCode === code || event.key === name;

Create a PredictiveSearchBox class. You’ll add methods in the following steps.

1
2
3
class PredictiveSearchBox {
  // Add methods to the class in the following steps
}

Add a constructor for the PredictiveSearchBox.

1
2
3
4
5
6
7
8
9
10
11
12
13
class PredictiveSearchBox {
  constructor(options) {
    Object.assign(this, options);

    this.client = algoliasearch(options.appID, options.apiKey);
    this.querySuggestionsIndex = this.client.initIndex(
      this.querySuggestionsIndex
    );

    this.maxSuggestions = 5;
    this.tabActionSuggestion = '';
  }
}

Add an initialization method for the PredictiveSearchBox class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class PredictiveSearchBox {
  // ...

  init(initOptions) {
    const widgetContainer = document.querySelector(this.container);

    if (!widgetContainer) {
      throw new Error(
        `Could not find widget container ${this.container} inside the DOM.`
      );
    }

    widgetContainer.innerHTML = renderSearchBoxContainer(
      this.placeholder,
      initOptions.helper.state.query
    );

    this.predictiveSearchBox = widgetContainer.querySelector('#predictive-box');
    this.predictiveSearchBoxItem = widgetContainer.querySelector(
      '#predictive-box-text'
    );
    this.clearButton = widgetContainer.querySelector('#clear-input');
    this.searchBoxInput = widgetContainer.querySelector('#search-box-input');
    this.suggestionTagsContainer = widgetContainer.querySelector(
      '#suggestion-tags-container'
    );

    this.registerSearchBoxHandlers(
      initOptions.helper,
      this.searchBoxInput,
      this.clearButton
    );
  }
}

Register handlers for searching, handling suggestions, tab completion, and keyboard navigation.

1
2
3
4
5
6
7
8
9
10
11
12
class PredictiveSearchBox {
  // ...

  registerSearchBoxHandlers = (helper, searchBox, clearButton) => {
    searchBox.addEventListener('input', event => this.updateTabActionSuggestion(event));
    searchBox.addEventListener('keydown', event => this.onTabSelection(event));
    clearButton.addEventListener('click', event => this.clear(event));
    searchBox.addEventListener('input', event => {
      helper.setQuery(event.currentTarget.value).search();
    });
  };
}

Add a setter method for the value of the search box input.

1
2
3
4
5
6
7
8
class PredictiveSearchBox {
  // ...

  setSearchBoxValue = value => {
    this.searchBoxInput.value = value;
    this.searchBoxInput.dispatchEvent(new Event('input'));
  };
}

Add a handler method that calls setSearchBoxValue to set the search box value to the suggestion displayed in the predictive box when users press the right arrow.

1
2
3
4
5
6
7
8
9
10
class PredictiveSearchBox {
  // ...

  onTabSelection = event => {
    if (!isKey(event, 39, 'ArrowRight')) return;

    event.preventDefault();
    this.setSearchBoxValue(this.tabActionSuggestion);
  };
}

Add a method that updates the suggestions tags based on the new hits from the query response.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class PredictiveSearchBox {
  // ...

  updateSuggestionTags = hits => {
    if (!this.maxSuggestions || this.maxSuggestions <= 0) return hits;
    this.clearSuggestionTags();

    hits.slice(0, this.maxSuggestions).forEach(suggestion => {
      const suggestionElement = document.createElement('button');
      suggestionElement.classList.add('suggestion-tag');
      suggestionElement.innerHTML = suggestion._highlightResult.query.value;

      suggestionElement.addEventListener('click', () => {
        this.setSearchBoxValue(suggestion.query);
      });
      this.suggestionTagsContainer.append(suggestionElement);
    });
  };
}

Add a method that dispatches a search on your Query Suggestions index and uses the results to update the suggestions in the predictive box and suggestions tags. This method triggers whenever the query changes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class PredictiveSearchBox {
  // ...

  updateTabActionSuggestion = event => {
    const query = event.currentTarget.value;

    if (!query) {
      this.predictiveSearchBox.style.display = 'none';
      this.clearButton.style.display = 'none';
      return;
    }

    this.querySuggestionsIndex
      .search({ query })
      .then(response => {
        const suggestions = response.hits.filter(hit =>
          hit.query.startsWith(query)
        );

        if (!suggestions.length) {
          this.clearPredictiveSearchBox();
          return [];
        }

        this.predictiveSearchBox.style.display = 'flex';
        this.predictiveSearchBoxItem.innerText = suggestions[0].query;
        this.tabActionSuggestion = suggestions[0].query;
        return suggestions.slice(1);
      })
      .then(this.updateSuggestionTags);
  };
}

Add a method that removes all suggestions tags so they can be updated.

1
2
3
4
5
6
7
class PredictiveSearchBox {
  // ...

  clearSuggestionTags = () => {
    this.suggestionTagsContainer.innerHTML = '';
  };
}

Add a method that removes the predictive box suggestion.

1
2
3
4
5
6
7
class PredictiveSearchBox {
  // ...

  clearPredictiveSearchBox = () => {
    this.tabActionSuggestion = '';
  };
}

Add a method that clears all suggestion content from your search container, letting you update it with new suggestions.

1
2
3
4
5
6
7
8
9
10
11
12
13
class PredictiveSearchBox {
  // ...

  clear = event => {
    this.searchBoxInput.value = '';
    this.predictiveSearchBoxItem.innerText = '';
    this.clearSuggestionTags();

    this.tabActionSuggestion = '';
    event.target.style.display = 'none';
    searchBoxInput.dispatchEvent(new Event('input'));
  };
}

Finally, export the PredictiveSearchBox class so you can import it into your app.

1
2
3
4
5
class PredictiveSearchBox {
  // ...
}

export default PredictiveSearchBox;