Displaying items with Templates
Once you’ve set up your data sources, you need to define how they display in your autocomplete experience. It encompasses the structure for each item and the way they look.
Autocomplete provides a Templates API to let you fully customize the render of each item.
Rendering each item
The rendering system of Autocomplete uses an agnostic virtual DOM implementation. This ensures great performance even with many renders, safety against cross-site scripting (XSS) attacks, inline event handling, and more.
You can return anything from each template as long as they’re valid virtual DOM elements (VNodes).
For example, templates can return a string:
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;
},
},
},
];
},
});
Or an HTML string using the provided 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>`;
},
},
},
];
},
});
Or a Preact component, 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 safely provide templates as an HTML string. It works directly in the browser, no need for a transpiler or a build step.
The html
function is only available starting from 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 }) {
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 provided components in your templates using either their function form, or by 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 }) {
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, you can leverage 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 }) {
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, you can use a short-circuit operator or a ternary.
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 }) {
return html`<div>
<img src="${item.image}" alt="${item.name}" />
<div>${item.name}</div>
<div>
${item.rating !== null ? `Rating: ${item.rating}` : 'Unrated'}
</div>
</div>`;
},
},
},
];
},
});
Internet Explorer 11 support
Tagged template notation isn’t supported in Internet Explorer 11, meaning you can’t use html
as a tagged template in this browser. 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 you’re already 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"]]
}
You can also get this transform as a standalone Babel plugin.
With a shim
If you don’t have a build step in your project, you can 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 either as static strings, or as 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. You can adapt it if you need a different behavior.
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, you can compile html
away using the babel-plugin-htm
Babel plugin.
To use this plugin, you need to adapt your code so that the pragma to replace html
calls with 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);
},
});
Then, you can 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, make sure to also transpile it using @babel/preset-env
.
Returning VNodes directly
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 internally 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.
Rendering a header and footer
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';
},
},
},
];
},
});
Using 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.
Highlighting and snippeting
Templates expose a set of built-in components
to handle highlighting and snippeting.
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>`;
},
},
},
];
},
});
Rendering a no results state
When there are no results, you might want to display a message to inform users or let them know what to do next. You can 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.';
},
},
},
];
},
});
Styling items
Since you’re fully controlling the rendered HTML, you can style it the way you want or use 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 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. |
footer
|
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. |