Autocomplete

Introduction

This tutorial will walk through how to display results “as you type” in an autocomplete menu using Algolia’s search engine. We’ll be building this feature with our autocomplete.js JavaScript library.

Let’s begin by examining a possible use case: searching for players on a team. An autocomplete dropdown will help users easily find the player they are looking for within a few keystrokes.

To build this autocomplete, we will need to:

  1. Import the dataset of players to Algolia
  2. Configure the ranking information
  3. Utilize autocomplete.js to develop a user interface

Importing Your Data

The first step in the process is to import your data to our servers either via the dashboard or the API. You can find more extensive information on importing and synchronizing your data in our import guide.

For this example, we will import player data (download JSON file) that includes the player’s name, team, and points. You can find a sample extract below.

[
  {
    "name": "Kevin Durant",
    "team": "Thunder",
    "points": 2472
  },
  {
    "name": "LeBron James",
    "team": "Cavaliers",
    "points": 2258
  },
  // [...]
]

Index Configuration

Configuring your index properly will help ensure that the most relevant results appear first for your users.

Searchable attributes

By default, all the attributes in your records will be searchable. For this example, we want users to be able to search by player name and team, but not by points. To accomplish this functionality, we will need to set up a list of attributes to search. This can be done either via an API client or directly on the dashboard under the Ranking tab. You can learn more in our searchable attributes guide.

Auto complete 1

Since searching for a player by name is our primary use case, we should indicate this attribute is more important by placing it at the top of the list.

Custom Ranking

To ensure users see the most relevant players, we will take into account the amount of points each player has scored (the higher the scores, the better). For that, we’ll configure the points attribute in the setting customRanking. Similar to the searchable attributes settings, this can be set up either via an API client or the dashboard (under the Ranking tab). You can refer to our custom ranking guide for additional information regarding custom ranking setup.

Auto complete 2

Since we would like higher scoring players to appear first, we have set the points attribute to descending sort order.

Alternatively, these settings can be configured via the API client (see example code snippets below).

Algolia.init_index('YourIndexName').set_settings(
  'searchableAttributes' => [
    'name',
    'team'
  ],
  'customRanking' => [
    'desc(points)'
  ]
)
<?php
$client->initIndex("YourIndexName")->setSettings(array(
  "searchableAttributes" => array(
    "name",
    "team"
  ),
   "customRanking" => array(
      "desc(points)"
  )
));
client.initIndex('YourIndexName').setSettings({
  "searchableAttributes": [
    "name",
    "team"
  ],
  "customRanking": [
    "desc(points)"
  ]
});
client.init_index('YourIndexName').set_settings({
    "searchableAttributes": [
        "name",
        "team"
    ],
    "customRanking": [
        "desc(points)"
    ]
})
client.InitIndex("YourIndexName").SetSettings(JObject.Parse(@"{
  ""searchableAttributes"":[
    ""name"",
    ""team""
  ],
  ""customRanking"":[
    ""desc(points)""
  ]
}"));

User Interface

The easiest way to render an autocomplete dropdown menu is to use our autocomplete.js JavaScript library. It provides an out of the box autocomplete menu that can be easily configured and integrated with Algolia’s realtime search engine.

We have created a live demo of a basic autocomplete menu using the autocomplete library that allows users to search for players. Try querying by both player name (e.g. “Kobe Bryant”) and team (e.g. “Lakers”)!

Try It - Live Demo

<!-- HTML Markup -->
<div class="aa-input-container" id="aa-input-container">
    <input type="search" id="aa-search-input" class="aa-input-search" placeholder="Search for players or teams..." name="search" autocomplete="off" />
    <svg class="aa-input-icon" viewBox="654 -372 1664 1664">
        <path d="M1806,332c0-123.3-43.8-228.8-131.5-316.5C1586.8-72.2,1481.3-116,1358-116s-228.8,43.8-316.5,131.5  C953.8,103.2,910,208.7,910,332s43.8,228.8,131.5,316.5C1129.2,736.2,1234.7,780,1358,780s228.8-43.8,316.5-131.5  C1762.2,560.8,1806,455.3,1806,332z M2318,1164c0,34.7-12.7,64.7-38,90s-55.3,38-90,38c-36,0-66-12.7-90-38l-343-342  c-119.3,82.7-252.3,124-399,124c-95.3,0-186.5-18.5-273.5-55.5s-162-87-225-150s-113-138-150-225S654,427.3,654,332  s18.5-186.5,55.5-273.5s87-162,150-225s138-113,225-150S1262.7-372,1358-372s186.5,18.5,273.5,55.5s162,87,225,150s113,138,150,225  S2062,236.7,2062,332c0,146.7-41.3,279.7-124,399l343,343C2305.7,1098.7,2318,1128.7,2318,1164z" />
    </svg>
</div>
<!-- Include AlgoliaSearch JS Client and autocomplete.js library -->
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.min.js"></script>
<!-- Initialize autocomplete menu -->
<script>
var client = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey');
var index = client.initIndex('YourIndex');
//initialize autocomplete on search input (ID selector must match)
autocomplete('#aa-search-input',
{ hint: false }, {
    source: autocomplete.sources.hits(index, {hitsPerPage: 5}),
    //value to be displayed in input control after user's suggestion selection
    displayKey: 'name',
    //hash of templates used when rendering dataset
    templates: {
        //'suggestion' templating function used to render a single suggestion
        suggestion: function(suggestion) {
          return '<span>' +
            suggestion._highlightResult.name.value + '</span><span>' +
            suggestion._highlightResult.team.value + '</span>';
        }
    }
});
</script>
<!-- HTML Markup -->
<div class="aa-input-container" id="aa-input-container" ng-controller="yourController">
    <input type="search" id="aa-search-input" class="aa-input-search" placeholder="Search for players or teams..." name="search" autocomplete="off"
    autocomplete aa-datasets="getDatasets()" aa-options="{hint: false}" /> <!-- Ensure autocomplete and aa-datasets attributes are added to your search input -->
    <svg class="aa-input-icon" viewBox="654 -372 1664 1664">
        <path d="M1806,332c0-123.3-43.8-228.8-131.5-316.5C1586.8-72.2,1481.3-116,1358-116s-228.8,43.8-316.5,131.5  C953.8,103.2,910,208.7,910,332s43.8,228.8,131.5,316.5C1129.2,736.2,1234.7,780,1358,780s228.8-43.8,316.5-131.5  C1762.2,560.8,1806,455.3,1806,332z M2318,1164c0,34.7-12.7,64.7-38,90s-55.3,38-90,38c-36,0-66-12.7-90-38l-343-342  c-119.3,82.7-252.3,124-399,124c-95.3,0-186.5-18.5-273.5-55.5s-162-87-225-150s-113-138-150-225S654,427.3,654,332  s18.5-186.5,55.5-273.5s87-162,150-225s138-113,225-150S1262.7-372,1358-372s186.5,18.5,273.5,55.5s162,87,225,150s113,138,150,225  S2062,236.7,2062,332c0,146.7-41.3,279.7-124,399l343,343C2305.7,1098.7,2318,1128.7,2318,1164z" />
    </svg>
</div>
<!-- Include AlgoliaSearch JS Client Angular module and autocomplete.js Angular directive after jQuery and Angular dependencies -->
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.angular.min.js"></script>
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.angular.min.js"></script>
<!-- Autocomplete angular module -->
<script>
 angular.module('myApp', ['algoliasearch', 'algolia.autocomplete'])
    .controller('yourController', ['$scope', 'algolia', function($scope, algolia) {
      var client = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey');
      var index = client.initIndex('YourIndex');
      $scope.getDatasets = function() {
        return {
          source: algolia.sources.hits(index, { hitsPerPage: 5 }),
          //value to be displayed in input control after user's suggestion selection
          displayKey: 'name',
          //hash of templates used when rendering dataset
          templates: {
            //'suggestion' templating function used to render a single suggestion
            suggestion: function(suggestion) {
                return '<span>' +
                    suggestion._highlightResult.name.value + '</span><span>' +
                    suggestion._highlightResult.team.value + '</span>';
            }
          }
        };
      };
    }]);
</script>
<!-- HTML Markup -->
<div class="aa-input-container" id="aa-input-container">
    <input type="search" id="aa-search-input" class="aa-input-search" placeholder="Search for players or teams..." name="search" autocomplete="off" />
    <svg class="aa-input-icon" viewBox="654 -372 1664 1664">
        <path d="M1806,332c0-123.3-43.8-228.8-131.5-316.5C1586.8-72.2,1481.3-116,1358-116s-228.8,43.8-316.5,131.5  C953.8,103.2,910,208.7,910,332s43.8,228.8,131.5,316.5C1129.2,736.2,1234.7,780,1358,780s228.8-43.8,316.5-131.5  C1762.2,560.8,1806,455.3,1806,332z M2318,1164c0,34.7-12.7,64.7-38,90s-55.3,38-90,38c-36,0-66-12.7-90-38l-343-342  c-119.3,82.7-252.3,124-399,124c-95.3,0-186.5-18.5-273.5-55.5s-162-87-225-150s-113-138-150-225S654,427.3,654,332  s18.5-186.5,55.5-273.5s87-162,150-225s138-113,225-150S1262.7-372,1358-372s186.5,18.5,273.5,55.5s162,87,225,150s113,138,150,225  S2062,236.7,2062,332c0,146.7-41.3,279.7-124,399l343,343C2305.7,1098.7,2318,1128.7,2318,1164z" />
    </svg>
</div>
<!-- Include AlgoliaSearch JS Client and autocomplete.js jQuery plugin after the jQuery dependency -->
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.jquery.min.js"></script>
<!-- Initialize autocomplete menu -->
<script>
  var client = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey');
  var index = client.initIndex('YourIndex');
  //initialize autocomplete on search input (ID selector must match)
  $('#aa-search-input').autocomplete(
    {hint: false}, [
    {
      source: $.fn.autocomplete.sources.hits(index, { hitsPerPage: 5 }),
      //value to be displayed in input control after user's suggestion selection
      displayKey: 'name',
      //hash of templates used when rendering dataset
      templates: {
        //'suggestion' templating function used to render a single suggestion
        suggestion: function(suggestion) {
          return '<span>' +
            suggestion._highlightResult.name.value + '</span><span>' +
            suggestion._highlightResult.team.value + '</span>';
        }
      }
    }
  ]);
</script>
.aa-input-container {
  display: inline-block;
  position: relative; }
.aa-input-search {
  width: 300px;
  border: 1px solid rgba(228, 228, 228, 0.6);
  padding: 12px 28px 12px 12px;
  box-sizing: border-box;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none; }
  .aa-input-search::-webkit-search-decoration, .aa-input-search::-webkit-search-cancel-button, .aa-input-search::-webkit-search-results-button, .aa-input-search::-webkit-search-results-decoration {
    display: none; }
.aa-input-icon {
  height: 16px;
  width: 16px;
  position: absolute;
  top: 50%;
  right: 16px;
  -webkit-transform: translateY(-50%);
          transform: translateY(-50%);
  fill: #e4e4e4; }
.aa-dropdown-menu {
  background-color: #fff;
  border: 1px solid rgba(228, 228, 228, 0.6);
  min-width: 300px;
  margin-top: 10px;
  box-sizing: border-box; }
.aa-suggestion {
  padding: 12px;
  cursor: pointer;
}
.aa-suggestion + .aa-suggestion {
    border-top: 1px solid rgba(228, 228, 228, 0.6);
}
  .aa-suggestion:hover, .aa-suggestion.aa-cursor {
    background-color: rgba(241, 241, 241, 0.35); }
@import 'https://fonts.googleapis.com/css?family=Montserrat:400,700';
.aa-input-container {
  display: inline-block;
  position: relative; }
.aa-input-search {
  width: 300px;
  padding: 12px 28px 12px 12px;
  border: 2px solid #e4e4e4;
  border-radius: 4px;
  -webkit-transition: .2s;
  transition: .2s;
  font-family: "Montserrat", sans-serif;
  box-shadow: 4px 4px 0 rgba(241, 241, 241, 0.35);
  font-size: 11px;
  box-sizing: border-box;
  color: #333;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none; }
  .aa-input-search::-webkit-search-decoration, .aa-input-search::-webkit-search-cancel-button, .aa-input-search::-webkit-search-results-button, .aa-input-search::-webkit-search-results-decoration {
    display: none; }
  .aa-input-search:focus {
    outline: 0;
    border-color: #3a96cf;
    box-shadow: 4px 4px 0 rgba(58, 150, 207, 0.1); }
.aa-input-icon {
  height: 16px;
  width: 16px;
  position: absolute;
  top: 50%;
  right: 16px;
  -webkit-transform: translateY(-50%);
          transform: translateY(-50%);
  fill: #e4e4e4; }
.aa-hint {
  color: #e4e4e4; }
.aa-dropdown-menu {
  background-color: #fff;
  border: 2px solid rgba(228, 228, 228, 0.6);
  border-top-width: 1px;
  font-family: "Montserrat", sans-serif;
  width: 300px;
  margin-top: 10px;
  box-shadow: 4px 4px 0 rgba(241, 241, 241, 0.35);
  font-size: 11px;
  border-radius: 4px;
  box-sizing: border-box; }
.aa-suggestion {
  padding: 12px;
  border-top: 1px solid rgba(228, 228, 228, 0.6);
  cursor: pointer;
  -webkit-transition: .2s;
  transition: .2s;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: justify;
      -ms-flex-pack: justify;
          justify-content: space-between;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center; }
  .aa-suggestion:hover, .aa-suggestion.aa-cursor {
    background-color: rgba(241, 241, 241, 0.35); }
  .aa-suggestion > span:first-child {
    color: #333; }
  .aa-suggestion > span:last-child {
    text-transform: uppercase;
    color: #a9a9a9; }
.aa-suggestion > span:first-child em, .aa-suggestion > span:last-child em {
  font-weight: 700;
  font-style: normal;
  background-color: rgba(58, 150, 207, 0.1);
  padding: 2px 0 2px 2px; }

The “CSS - Basic” tab contains out-of-the-box structural styles. For the fully-themed version (as shown in the live demo), copy the “CSS - Themed” tab.

Tip: Try out our Searchbox tool for easy search input styling!

Options

The autocomplete.js library provides several options out of the box for further customization. These can be configured on initialize of an autocomplete menu. In the example above, setting hint: false disables the hint text that by default appears in the search input.

Tip: Setting debug: true will prevent auto-closing of the menu and make styling easier!

Highlighting Results

Technically, suggestion attributes can be accessed in the suggestion templating function at suggestion.attribute_name. However, we recommend accessing the content of suggestion attributes at suggestion._highlightResult.attribute_name.value. As a result, matched words will be wrapped with an <em> HTML tag, which can be styled to indicate to users what portion of their query matches a suggestion. Leveraging this functionality helps users better understand the relevance of the results in relation to their query.

Leveraging Custom Events

We can improve the user experience of our autocomplete menu by adding a common UI pattern: an “X” icon that appears when text has been entered that allows users to easily clear their queries. To add this functionality, we will take advantage of the custom events that the autocomplete.js library triggers.

You can find a working demo of this type of search experience below. Try entering a query, and then clearing it by clicking the “X” icon!

Try It - Live Demo

<!-- HTML Markup -->
<div class="aa-input-container" id="aa-input-container">
    <input type="search" id="aa-search-input" class="aa-input-search" placeholder="Search for players or teams..." name="search" autocomplete="off" />
    <svg id="icon-search" class="aa-input-icon" viewBox="654 -372 1664 1664">
        <path d="M1806,332c0-123.3-43.8-228.8-131.5-316.5C1586.8-72.2,1481.3-116,1358-116s-228.8,43.8-316.5,131.5  C953.8,103.2,910,208.7,910,332s43.8,228.8,131.5,316.5C1129.2,736.2,1234.7,780,1358,780s228.8-43.8,316.5-131.5  C1762.2,560.8,1806,455.3,1806,332z M2318,1164c0,34.7-12.7,64.7-38,90s-55.3,38-90,38c-36,0-66-12.7-90-38l-343-342  c-119.3,82.7-252.3,124-399,124c-95.3,0-186.5-18.5-273.5-55.5s-162-87-225-150s-113-138-150-225S654,427.3,654,332  s18.5-186.5,55.5-273.5s87-162,150-225s138-113,225-150S1262.7-372,1358-372s186.5,18.5,273.5,55.5s162,87,225,150s113,138,150,225  S2062,236.7,2062,332c0,146.7-41.3,279.7-124,399l343,343C2305.7,1098.7,2318,1128.7,2318,1164z" />
    </svg>
    <svg class="aa-input-close" id="icon-close" viewBox="0 0 26 25">
        <polygon points="26.2,23 15.4,12.5 26.2,2 23.9,-0.4 13,10.2 2.1,-0.4 -0.2,2 10.6,12.5 -0.2,23 2.1,25.4 13,14.8     23.9,25.4" />
    </svg> <!-- Added SVG close icon -->
</div>
<!-- JavaScript updates -->
<script>
//DOM Binding
var searchInput = document.getElementById("aa-search-input");
var inputContainer = document.getElementById("aa-input-container");
//Handle add/removing a class based on if text has been entered in the search input
//attach custom event handler - autocomplete:updated triggers when dataset is rendered
autocomplete('#aa-search-input', {}, [...]).on('autocomplete:updated', function() {
    if (searchInput.value.length > 0) {
        inputContainer.classList.add("input-has-value");
    }
    else {
        inputContainer.classList.remove("input-has-value");
    }
});
//Handle clearing the search input on close icon click
document.getElementById("icon-close").addEventListener("click", function() {
    searchInput.value = "";
    inputContainer.classList.remove("input-has-value");
});
</script>

For this use case, we need to attach the autocomplete:updated custom event handler to the autocomplete element. This event will be triggered every time a dataset is rendered. Inside this method, we need to check if text has been entered in the search input field, and then add a class to the parent container indicating if the child input has value. If the search input has no text entered in it, we will remove the class from the parent container.

Additionally, we added a click event onto the new search icon to handle clearing the search input and removing the respective class from the parent container.

To correctly show or hide the appropriate icons based on our new class, we will need additional CSS.

.input-has-value .aa-input-icon {
  opacity: 0; }
.aa-input-close {
  height: 12px;
  width: 12px;
  position: absolute;
  top: 50%;
  right: 16px;
  -webkit-transform: translateY(-50%);
          transform: translateY(-50%);
  fill: #e4e4e4;
  visibility: hidden;
  opacity: 0; }
  .input-has-value .aa-input-close {
    opacity: 1;
    visibility: visible;
    cursor: pointer; }
.input-has-value .aa-input-icon {
  opacity: 0; }
.aa-input-close {
  height: 12px;
  width: 12px;
  position: absolute;
  top: 50%;
  right: 16px;
  -webkit-transform: translateY(-50%);
          transform: translateY(-50%);
  fill: #e4e4e4;
  visibility: hidden;
  opacity: 0; }
  .input-has-value .aa-input-close {
    opacity: 1;
    visibility: visible;
    cursor: pointer;
    -webkit-transition: .2s ease-in;
    transition: .2s ease-in; }
    .input-has-value .aa-input-close:hover {
      fill: #333; }

When the .input-has-value class has been added to the container, we hide the general input search icon. Conversely, the ‘X’ icon is hidden by default, and then shown when the parent container has the .input-has-value class.

Multi-Category

Our existing autocomplete could be improved by adding the ability for users to search not only player but also team information. To accomplish this, we will need to create a new “team” index and import team data (download JSON file). You can view a sample extract of the team data below.

Different types of data should be created as different indices - this allows you to create separate ranking strategies more tailored to each type of data.

[
  {
    "name": "Hawks",
    "location": "Atlanta",
    "logoUrl": "Hawks_Atlanta.gif",
    "score": 595.5714285714286
  },
  {
    "name": "Celtics",
    "location": "Boston",
    "logoUrl": "Celtics_Boston.gif",
    "score": 428.2105263157895
  }
  // [...]
]

Just as we did for our initial players index, we will need to specify both searchable attributes and custom ranking criterion. In this case, it makes sense to allow users to search by team name or location, as both of these attributes will be displayed. To rank the teams, we have added a “score” attribute that reflects the average of the points of a team’s respective players.

Auto complete 3

Algolia.init_index('YourIndexName').set_settings({
  "searchableAttributes"=>[
    "name",
    "location"
  ],
  "customRanking"=>[
    "asc(name)"
  ]
})
<?php
$client->initIndex("YourIndexName")->setSettings(array(
  "searchableAttributes" => array(
    "name",
    "location"
  ),
  "customRanking" => array(
    "asc(name)"
  )
));
client.initIndex('YourIndexName').setSettings({
  "searchableAttributes":[
    "name",
    "location"
  ],
  "customRanking":[
    "asc(name)"
  ]
});
client.init_index('YourIndexName').set_settings({
  "searchableAttributes":[
    "name",
    "location"
  ],
  "customRanking":[
    "asc(name)"
  ]
})

Multi-Category User Interface

In our demo below, we display both player and team results with their respective searchable attributes. To help users contextualize the suggestions, we have included category headers (e.g., the player information has a “Players” title). Additionally, we reduced the hits per page from 5 to 3 to prevent an overly lengthy menu.

Try It - Live Demo

<!-- HTML Markup -->
<div class="aa-input-container" id="aa-input-container">
    <input type="search" id="aa-search-input" class="aa-input-search" placeholder="Search for players or teams..." name="search" autocomplete="off" />
    <svg class="aa-input-icon" viewBox="654 -372 1664 1664">
        <path d="M1806,332c0-123.3-43.8-228.8-131.5-316.5C1586.8-72.2,1481.3-116,1358-116s-228.8,43.8-316.5,131.5  C953.8,103.2,910,208.7,910,332s43.8,228.8,131.5,316.5C1129.2,736.2,1234.7,780,1358,780s228.8-43.8,316.5-131.5  C1762.2,560.8,1806,455.3,1806,332z M2318,1164c0,34.7-12.7,64.7-38,90s-55.3,38-90,38c-36,0-66-12.7-90-38l-343-342  c-119.3,82.7-252.3,124-399,124c-95.3,0-186.5-18.5-273.5-55.5s-162-87-225-150s-113-138-150-225S654,427.3,654,332  s18.5-186.5,55.5-273.5s87-162,150-225s138-113,225-150S1262.7-372,1358-372s186.5,18.5,273.5,55.5s162,87,225,150s113,138,150,225  S2062,236.7,2062,332c0,146.7-41.3,279.7-124,399l343,343C2305.7,1098.7,2318,1128.7,2318,1164z" />
    </svg>
</div>
<!-- Include AlgoliaSearch JS Client and autocomplete.js library -->
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.min.js"></script>
<!-- Initialize autocomplete menu -->
<script>
var client = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey')
var players = client.initIndex('players');
var teams = client.initIndex('teams');

autocomplete('#aa-search-input', {}, [
    {
      source: autocomplete.sources.hits(players, { hitsPerPage: 3 }),
      displayKey: 'name',
      templates: {
        header: '<div class="aa-suggestions-category">Players</div>',
        suggestion: function(suggestion) {
          return '<span>' +
            suggestion._highlightResult.name.value + '</span><span>'
              + suggestion._highlightResult.team.value + '</span>';
        }
      }
    },
    {
      source: autocomplete.sources.hits(teams, { hitsPerPage: 3 }),
      displayKey: 'name',
      templates: {
        header: '<div class="aa-suggestions-category">Teams</div>',
        suggestion: function(suggestion) {
          return '<span>' +
            suggestion._highlightResult.name.value + '</span><span>'
              + suggestion._highlightResult.location.value + '</span>';
        }
      }
    }
]);
</script>

Tip: Use the header template to provide category headers!

.aa-input-container {
  display: inline-block;
  position: relative; }
.aa-input-search {
  width: 300px;
  padding: 12px 28px 12px 12px;
  border: 1px solid #e4e4e4;
  box-sizing: border-box;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none; }
  .aa-input-search::-webkit-search-decoration, .aa-input-search::-webkit-search-cancel-button, .aa-input-search::-webkit-search-results-button, .aa-input-search::-webkit-search-results-decoration {
    display: none; }
.aa-input-icon {
  height: 16px;
  width: 16px;
  position: absolute;
  top: 50%;
  right: 16px;
  -webkit-transform: translateY(-50%);
          transform: translateY(-50%);
  fill: #e4e4e4;
  pointer-events: none; }
.aa-dropdown-menu {
  background-color: #fff;
  border: 1px solid rgba(228, 228, 228, 0.6);
  min-width: 300px;
  margin-top: 10px;
  box-sizing: border-box; }
.aa-suggestion {
  padding: 6px 12px;
  cursor: pointer;}
.aa-suggestion + .aa-suggestion {
    border-top: 1px solid rgba(228, 228, 228, 0.6);
    }
.aa-suggestions-category {
  border-bottom: 1px solid rgba(228, 228, 228, 0.6);
  border-top: 1px solid rgba(228, 228, 228, 0.6);
  padding: 6px 12px; }
@import 'https://fonts.googleapis.com/css?family=Montserrat:400,700';
.aa-input-container {
  display: inline-block;
  position: relative; }
.aa-input-search {
  width: 300px;
  padding: 12px 28px 12px 12px;
  border: 2px solid #e4e4e4;
  border-radius: 4px;
  -webkit-transition: .2s;
  transition: .2s;
  font-family: "Montserrat", sans-serif;
  box-shadow: 4px 4px 0 rgba(241, 241, 241, 0.35);
  font-size: 11px;
  box-sizing: border-box;
  color: #333;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none; }
  .aa-input-search::-webkit-search-decoration, .aa-input-search::-webkit-search-cancel-button, .aa-input-search::-webkit-search-results-button, .aa-input-search::-webkit-search-results-decoration {
    display: none; }
  .aa-input-search:focus {
    outline: 0;
    border-color: #3a96cf;
    box-shadow: 4px 4px 0 rgba(58, 150, 207, 0.1); }
.aa-input-icon {
  height: 16px;
  width: 16px;
  position: absolute;
  top: 50%;
  right: 16px;
  -webkit-transform: translateY(-50%);
          transform: translateY(-50%);
  fill: #e4e4e4;
  pointer-events: none; }
.aa-hint {
  color: #e4e4e4; }
.aa-dropdown-menu {
  background-color: #fff;
  border: 2px solid rgba(228, 228, 228, 0.6);
  border-top-width: 0;
  font-family: "Montserrat", sans-serif;
  width: 300px;
  margin-top: 10px;
  box-shadow: 4px 4px 0 rgba(241, 241, 241, 0.35);
  font-size: 11px;
  border-radius: 4px;
  box-sizing: border-box; }
.aa-suggestion {
  padding: 6px 12px;
  cursor: pointer;
  -webkit-transition: .2s;
  transition: .2s;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: justify;
      -ms-flex-pack: justify;
          justify-content: space-between;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center; }
  .aa-suggestion:hover, .aa-suggestion.aa-cursor {
    background-color: rgba(241, 241, 241, 0.35); }
  .aa-suggestion > span:first-child {
    color: #333; }
  .aa-suggestion > span:last-child {
    text-transform: uppercase;
    color: #a9a9a9; }
.aa-suggestions-category {
  text-transform: uppercase;
  border-bottom: 2px solid rgba(228, 228, 228, 0.6);
  border-top: 2px solid rgba(228, 228, 228, 0.6);
  padding: 6px 12px;
  color: #a9a9a9; }
.aa-suggestion > span:first-child em, .aa-suggestion > span:last-child em {
  font-weight: 700;
  font-style: normal;
  background-color: rgba(58, 150, 207, 0.1);
  padding: 2px 0 2px 2px; }

Rich Horizontal User Interface

For multi-category autocomplete menus, a horizontal display often provides a better user experience than a vertical display. The increased size allows for more results to be displayed without the need for scrolling, which helps users more easily find information. We can also take advantage of the additional space to display images of the team logos.

With a few additional lines of CSS, we can easily convert our existing vertical display to be horizontal.

Try It - Live Demo

<!-- HTML Markup -->
<div class="aa-input-container" id="aa-input-container">
    <input type="search" id="aa-search-input" class="aa-input-search" placeholder="Search for players or teams..." name="search" autocomplete="off" />
    <svg class="aa-input-icon" viewBox="654 -372 1664 1664">
        <path d="M1806,332c0-123.3-43.8-228.8-131.5-316.5C1586.8-72.2,1481.3-116,1358-116s-228.8,43.8-316.5,131.5  C953.8,103.2,910,208.7,910,332s43.8,228.8,131.5,316.5C1129.2,736.2,1234.7,780,1358,780s228.8-43.8,316.5-131.5  C1762.2,560.8,1806,455.3,1806,332z M2318,1164c0,34.7-12.7,64.7-38,90s-55.3,38-90,38c-36,0-66-12.7-90-38l-343-342  c-119.3,82.7-252.3,124-399,124c-95.3,0-186.5-18.5-273.5-55.5s-162-87-225-150s-113-138-150-225S654,427.3,654,332  s18.5-186.5,55.5-273.5s87-162,150-225s138-113,225-150S1262.7-372,1358-372s186.5,18.5,273.5,55.5s162,87,225,150s113,138,150,225  S2062,236.7,2062,332c0,146.7-41.3,279.7-124,399l343,343C2305.7,1098.7,2318,1128.7,2318,1164z" />
    </svg>
</div>
<!-- Include AlgoliaSearch JS Client and autocomplete.js library -->
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.min.js"></script>
<!-- Initialize autocomplete menu -->
<script>
var client = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey')
var players = client.initIndex('players');
var teams = client.initIndex('teams');

autocomplete('#aa-search-input', {debug: true, templates: {
    dropdownMenu:
    '<div class="aa-dataset-player"></div>' +
    '<div class="aa-dataset-team"></div>'
    }}, [
    {
      source: autocomplete.sources.hits(players, { hitsPerPage: 7 }),
      displayKey: 'name',
      name: 'player',
      templates: {
        header: '<div class="aa-suggestions-category">Players</div>',
        suggestion: function(suggestion) {
          return '<span>' +
            suggestion._highlightResult.name.value + '</span><span>'
              + suggestion._highlightResult.team.value + '</span>';
        },
        empty: '<div class="aa-empty">No matching players</div>'
      }
    },
    {
      source: autocomplete.sources.hits(teams, { hitsPerPage: 5 }),
      displayKey: 'name',
      name: 'team',
      templates: {
        header: '<div class="aa-suggestions-category">Teams</div>',
        suggestion: function(suggestion) {
          return '<img src="' + suggestion.logoUrl + '">' + '<div><span>' +
            suggestion._highlightResult.name.value + '</span><span>' +
            suggestion._highlightResult.location.value + '</span></div>';
        },
        empty: '<div class="aa-empty">No matching teams</div>'
      }
    }
]);
</script>

Results for players will be automatically injected in the <div class=”aa-dataset-player”></div> container. The value of name is used to associate the correct results with the correct aa-dataset-* container. 

Tip: Use the empty template to let users know when no results have been found!

.aa-input-container {
  display: inline-block;
  position: relative; }
.aa-input-search {
  width: 300px;
  padding: 12px 28px 12px 12px;
  border: 1px solid #e4e4e4;
  box-sizing: border-box;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none; }
  .aa-input-search::-webkit-search-decoration, .aa-input-search::-webkit-search-cancel-button, .aa-input-search::-webkit-search-results-button, .aa-input-search::-webkit-search-results-decoration {
    display: none; }
.aa-input-icon {
  height: 16px;
  width: 16px;
  position: absolute;
  top: 50%;
  right: 16px;
  -webkit-transform: translateY(-50%);
          transform: translateY(-50%);
  fill: #e4e4e4;
  pointer-events: none; }
.aa-dropdown-menu {
  background-color: #fff;
  border: 1px solid rgba(228, 228, 228, 0.6);
  min-width: 300px;
  margin-top: 10px;
  box-sizing: border-box; }
.aa-suggestion {
  padding: 6px 12px;
  cursor: pointer;}
.aa-suggestions-category {
  border-bottom: 1px solid rgba(228, 228, 228, 0.6);
  border-top: 1px solid rgba(228, 228, 228, 0.6);
  padding: 6px 12px; }
.aa-dropdown-menu {
  min-width: 600px; }
  .aa-dropdown-menu > div {
    display: inline-block;
    width: 50%;
    vertical-align: top; }
.aa-empty {
  padding: 6px 12px; }
@import 'https://fonts.googleapis.com/css?family=Montserrat:400,700';
.aa-input-container {
  display: inline-block;
  position: relative; }
.aa-input-search {
  width: 300px;
  padding: 12px 28px 12px 12px;
  border: 2px solid #e4e4e4;
  border-radius: 4px;
  -webkit-transition: .2s;
  transition: .2s;
  font-family: "Montserrat", sans-serif;
  box-shadow: 4px 4px 0 rgba(241, 241, 241, 0.35);
  font-size: 11px;
  box-sizing: border-box;
  color: #333;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none; }
  .aa-input-search::-webkit-search-decoration, .aa-input-search::-webkit-search-cancel-button, .aa-input-search::-webkit-search-results-button, .aa-input-search::-webkit-search-results-decoration {
    display: none; }
  .aa-input-search:focus {
    outline: 0;
    border-color: #3a96cf;
    box-shadow: 4px 4px 0 rgba(58, 150, 207, 0.1); }
.aa-input-icon {
  height: 16px;
  width: 16px;
  position: absolute;
  top: 50%;
  right: 16px;
  -webkit-transform: translateY(-50%);
          transform: translateY(-50%);
  fill: #e4e4e4;
  pointer-events: none; }
.aa-hint {
  color: #e4e4e4; }
.aa-dropdown-menu {
  background-color: #fff;
  border: 2px solid rgba(228, 228, 228, 0.6);
  border-top-width: 0;
  font-family: "Montserrat", sans-serif;
  width: 300px;
  margin-top: 10px;
  box-shadow: 4px 4px 0 rgba(241, 241, 241, 0.35);
  font-size: 11px;
  border-radius: 4px;
  box-sizing: border-box; }
.aa-suggestion {
  padding: 6px 12px;
  cursor: pointer;
  -webkit-transition: .2s;
  transition: .2s;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: justify;
      -ms-flex-pack: justify;
          justify-content: space-between;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center; }
  .aa-suggestion:hover, .aa-suggestion.aa-cursor {
    background-color: rgba(241, 241, 241, 0.35); }
  .aa-suggestion > div {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -webkit-box-pack: justify;
        -ms-flex-pack: justify;
            justify-content: space-between;
    -webkit-box-align: center;
        -ms-flex-align: center;
            align-items: center;
    width: 100%; }
  .aa-suggestion span:first-child {
    color: #333; }
  .aa-suggestion span:last-child {
    text-transform: uppercase;
    color: #a9a9a9; }
  .aa-suggestion img {
    max-width: 80px;
    margin-right: 10px; }
.aa-suggestions-category {
  text-transform: uppercase;
  border-bottom: 2px solid rgba(228, 228, 228, 0.6);
  border-top: 2px solid rgba(228, 228, 228, 0.6);
  padding: 10px;
  color: #a9a9a9;
  padding: 6px 12px;
  text-align: left; }
.aa-suggestion span:first-child em, .aa-suggestion span:last-child em {
  font-weight: 700;
  font-style: normal;
  background-color: rgba(58, 150, 207, 0.1);
  padding: 2px 0 2px 2px; }
.aa-dropdown-menu {
  width: 600px; }
  .aa-dropdown-menu > div {
    display: inline-block;
    width: 50%;
    vertical-align: top; }
.aa-empty {
  padding: 6px 12px; }

Media-rich multi-category autocomplete menus offer many possibilities for engaging users. You can find an example of a more complex multi-category dropdown menu live on birchbox.fr.

Auto complete 4

View the Birchbox live example!

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!