UI libraries / Autocomplete / Core concepts

Displaying items with templates

Once you’ve set up your data sources, you need to define how they display. Autocomplete templates let you customize the appearance and layout of each item.

Render each item

Autocomplete uses a virtual DOM for rendering. This ensures optimal performance even with frequent updates, safeguards against cross-site scripting (XSS) attacks, and supports inline event handling.

Templates can return any valid virtual DOM elements (VNodes).

For example, templates can return strings, HTML strings, or Preact components.

Strings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item }) {
            return item.name;
          },
        },
      },
    ];
  },
});

HTML strings

Templates can return HTML strings using an html tagged template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item, html }) {
            return html`<div>${item.name}</div>`;
          },
        },
      },
    ];
  },
});

Preact components

Templates can return Preact components using JSX:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** @jsx h */
import { h } from 'preact';
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item }) {
            return <div>{item.name}</div>;
          },
        },
      },
    ];
  },
});

Autocomplete uses Preact 10 to render templates by default. It isn’t compatible with earlier versions.

Returning HTML

Native HTML elements aren’t valid VNodes, which means you can’t return a template string that contains HTML, or an HTML element. But if you’re not using a virtual DOM implementation in your app, you can still return HTML with the provided html tagged template.

Every Autocomplete template provides an html function that you can use as a tagged template. Using html lets you provide templates as an HTML string It works directly in the browser without needing a transpiler or a build step.

The html function is only available starting from Autocomplete v1.6.0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item, html }) {
            return html`<div>
              <img src="${item.image}" alt="${item.name}" />
              <div>${item.name}</div>
            </div>`;
          },
        },
      },
    ];
  },
});

Internet Explorer 11 doesn’t support tagged template literals. If you need to support Internet Explorer 11, check out the suggested solutions.

Components and layouts

You can use the provided components in your templates by using their function form or interpolating them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item, components, html }) {
            return html`<div>
              <img src="${item.image}" alt="${item.name}" />
              <div>
                ${components.Highlight({ hit: item, attribute: 'name' })}
              </div>
            </div>`;
          },
        },
      },
    ];
  },
});

The html function is also exposed in render and renderNoResults to customize the panel layout.

Loops and conditional rendering

You can use plain JavaScript to build dynamic templates.

For example, use Array.map to loop over an array and display a list.

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
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item, html }) {
            return html`<div>
              <img src="${item.image}" alt="${item.name}" />
              <div>${item.name}</div>
              <ul>
                ${item.categories.map(
                  (category) =>
                    html`<li key="${category.id}">${category.label}</li>`
                )}
              </ul>
            </div>`;
          },
        },
      },
    ];
  },
});

Passing a unique key attribute is helpful when mapping over items. It helps the virtual DOM keep track of each element when they change, and update the UI efficiently.

To conditionally render a part of your UI, use a short-circuit or a ternary operator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item, html }) {
            return html`<div>
              <img src="${item.image}" alt="${item.name}" />
              <div>${item.name}</div>
              <div>
                ${item.rating !== null ? `Rating: ${item.rating}` : 'Unrated'}
              </div>
            </div>`;
          },
        },
      },
    ];
  },
});

HTML in variables

Only the HTML provided as a string literal is evaluated. HTML in variables (for example, stored in Algolia records) isn’t supported and is rendered as-is.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item, html }) {
            const name = '<p>Jimmie Barninger</p>';

            // This returns a `<div>` with text "<p>Jimmie Barninger</p>"
            return html`<div>${name}</div>`;
          },
        },
      },
    ];
  },
});

Internet Explorer 11 support

You can’t use html as a tagged template in Internet Explorer 11. Depending on your project setup, you can work around this problem to still use html while providing compatible code to your Internet Explorer 11 users.

With Babel

If you have a build step and are using Babel to compile your code for legacy browsers, you can transform all html expressions into regular function calls.

The recommended setup is to use @babel/preset-env, which provides this transformation along with other common ones based on a list of browsers to support.

1
2
3
{
  "presets": [["@babel/preset-env"]]
}

This transform is available as a Babel plugin.

With a shim

If you don’t have a build step in your project, write a shim. Tagged templates are regular functions with a specific signature, so you can wrap their calls with a friendlier API to avoid using tagged template notation.

This function takes templates as static strings or an array of interspersed chunks, splits them, and passes them to the html function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function htmlShim(template, html) {
  if (typeof template === 'string') {
    return html([template]);
  }

  const parts = template.reduce(
    (acc, part, index) => {
      const isEven = index % 2 === 0;

      acc[Math.abs(Number(!isEven))].push(part);

      return acc;
    },
    [[], []]
  );

  return html(parts[0], ...parts[1]);
}

The documented shim assumes every even array entry is a template string, and every odd entry is a dynamic value. Change the shim if you need it to do something different.

Further optimizations

The provided html function works in the browser without any build step, with a negligible impact on memory and bundle size (< 600 bytes).

For optimal performance, use babel-plugin-htm to compile html. To use this plugin, adapt your code so that the compiler directive to replace html calls is always accessible. For example, instead of destructuring in the signature, you need to name the parameter and destructure it in the function body—or not destructure it at all. The parameter (here, params) must have the same name in every template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
-         item({ item, html }) {
+         item(params) {
+           const { item, html } = params;

            return html`<div>${item.name}</div>`;
          },
        },
      },
    ];
  },
- render({ children, render, html }, root) {
+ render(params, root) {
+   const { children, render, html } = params;

    render(html`<div>${children}</div>`, root);
  },
});

Set up the Babel plugin.

1
2
3
4
5
6
7
8
9
10
{
  "plugins": [
    [
      "htm",
      {
        "pragma": "params.createElement"
      }
    ]
  ]
}

If you’re destructuring objects, ensure you also transpile it using @babel/preset-env.

Return virtual nodes directly

You can return virtual nodes with JSX or createElement.

Using JSX

The JSX syntax compiles down to VNodes. If you’re using JSX in your project, you can directly return JSX templates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** @jsx h */
import { h } from 'preact';
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item }) {
            return <div>{item.name}</div>;
          },
        },
      },
    ];
  },
});

By default, Autocomplete uses Preact 10 to render templates. If you’re using another virtual DOM implementation, you can pass a custom renderer.

Using createElement

Each template function provides access to createElement and Fragment to create VNodes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { autocomplete } from '@algolia/autocomplete-js';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item, createElement, Fragment }) {
            return createElement(Fragment, {}, item.name);
          },
        },
      },
    ];
  },
});

By default, createElement and Fragment default to Preact’s preact.createElement (or h) and preact.Fragment. If you’re using another virtual DOM implementation, you can replace them.

In addition to rendering items, you can customize what to display before and after the list of items using the header and footer templates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
autocomplete({
  // ...
  getSources({ query }) {
    return [
      {
        // ...
        templates: {
          header() {
            return 'Suggestions';
          },
          item({ item }) {
            return `Result: ${item.name}`;
          },
          footer() {
            return 'Footer';
          },
        },
      },
    ];
  },
});

Components

Autocomplete exposes components to all templates to share them everywhere in the instance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
autocomplete({
  // ...
  getSources({ query }) {
    return [
      {
        getItems() {
          return [
            /* ... */
          ];
        },
        templates: {
          item({ item, components }) {
            return components.Highlight({ hit: item, attribute: 'name' });
          },
        },
      },
    ];
  },
});

Four components are registered by default:

  • Highlight to highlight matches in Algolia results.
  • Snippet to snippet matches in Algolia results.
  • ReverseHighlight to reverse highlight matches in Algolia results.
  • ReverseSnippet to reverse highlight and snippet matches in Algolia results.

Highlight and snippet

Templates expose a set of built-in components to handle highlighting and snippeting. Use these components to highlight or snippet results from the getAlgoliaResults function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
autocomplete({
  // ...
  getSources({ query }) {
    return [
      {
        // ...
        templates: {
          item({ item, components, html }) {
            return html`<div>
              <img class="thumbnail" src="${item.image}" />
              <a href="${item.url}">
                ${components.Highlight({
                  hit: item,
                  attribute: 'name',
                  tagName: 'em',
                })}
              </a>
            </div>`;
          },
        },
      },
    ];
  },
});

Render a no-results state

If there are no results, you might want to display a message to inform users or let them know what to do next. Do this with the noResults template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
autocomplete({
  // ...
  getSources({ query }) {
    return [
      {
        // ...
        templates: {
          // ...
          noResults() {
            return 'No results.';
          },
        },
      },
    ];
  },
});

Style items

Since you’re fully controlling the rendered HTML, style it how you want using any class-based CSS library. For example, if you’re using Bootstrap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { autocomplete } from '@algolia/autocomplete-js';

import 'https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item, html }) {
            return html`<div class="list-group-item-action">${item.name}</div>`;
          },
        },
      },
    ];
  },
});

Or Tailwind CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { autocomplete } from '@algolia/autocomplete-js';

import 'https://unpkg.com/tailwindcss@latest/dist/tailwind.min.css';

autocomplete({
  // ...
  getSources() {
    return [
      {
        // ...
        templates: {
          item({ item, html }) {
            return html`<div
              class="py-2 px-4 rounded-sm border border-gray-200"
            >
              ${item.name}
            </div>`;
          },
        },
      },
    ];
  },
});

Reference

templates
type: AutocompleteTemplates

A set of templates to customize how items are displayed. You can also provide templates for header and footer elements around the list of items.

You must define templates within your sources.

See template for what to return.

template ➔ template

header
type: (params: { state: AutocompleteState<TItem>, source: AutocompleteSource<TItem>, items: TItem[], createElement: Pragma, Fragment: PragmaFrag, components: AutocompleteComponents }) => VNode | string

A function that returns the template for the header (before the list of items).

item
type: (params: { item: TItem, state: AutocompleteState<TItem>, createElement: Pragma, Fragment: PragmaFrag, components: AutocompleteComponents }) => VNode | string

A function that returns the template for each item of the source.

type: (params: { state: AutocompleteState<TItem>, source: AutocompleteSource<TItem>, items: TItem[], createElement: Pragma, Fragment: PragmaFrag, components: AutocompleteComponents }) => VNode | string

A function that returns the template for the footer (after the list of items).

noResults
type: (params: { state: AutocompleteState<TItem>, source: AutocompleteSource<TItem>, createElement: Pragma, Fragment: PragmaFrag, components: AutocompleteComponents }) => VNode | string

A function that returns the template for when there are no items.

Did you find this page helpful?