Guides / Solutions / Ecommerce / Search / Autocomplete

Predictive Search Suggestions

This guide uses InstantSearch.js to implement a custom predictive text search box widget.

Predictive search suggestions show the top search suggestion for a user’s input directly inside the search box. This solution lets users quickly access the content they’re looking for, because relevant suggestions appear directly inside the search box. They can also see the most popular searches, and discover more of your catalog.

Solution requirements

Difficulty
Intermediate
Features Query Suggestions
Prerequisites Instant Search 3+

Implementation guide

To create a predictive search box, you first need to create a Query Suggestions index. If you already have one, you can go directly to the detailed implementation overview.

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.

This is a brief guide to creating a Query Suggestions index. 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 display further results under the search box, in a dropdown or as 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 user’s 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 the creation of a predictive search box widget, which you can then import in your application.

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

  • the search box,
  • the overlapping predictive box,
  • and 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 the user types 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 query),
  • 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 full CSS in this guide. If you want to, you can find the complete CSS in the solution source code.

Define a helper function that checks whether the user has 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 that is displayed in the predictive box when the user presses 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 that 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 in your application.

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

export default PredictiveSearchBox;
Did you find this page helpful?