Visual facets
On this page
Visual facets let you enrich your filtering UI with visual indications such as category images, colors, or logos. With visual facets, you can create better-looking layouts and search experiences. Visual facets are typically more engaging than plain text facets, thus increasing user engagement and encouraging discovery.
Before you begin
This tutorial requires InstantSearch.js.
Implementation guide
This guide provides two implementation options for visual facets: you can either add the necessary information to your records or do the mapping directly on your frontend.
Option 1: Adding visual facets to your records
One way of implementing visual facets is by embedding the information you’re trying to display directly in your facet names.
If you’re doing this on an existing Algolia index, you must update all your records.
Set the facet attributes of all your records with the following pattern:
- The visual information (in this case, the URL of the image or the color code),
||
as a separator,- The regular facet value.
1
2
3
4
5
{
"name": "Black T-shirt",
"color": "#000000||black",
"availableIn": "https://source.unsplash.com/100x100/?paris||Paris"
}
If you’re creating a new index, use the saveObjects
method with the just-mentioned facet values:
1
2
3
4
5
6
7
8
$res = $index->saveObjects([
[
'name' => 'Black T-shirt',
'color' => '#000000||black',
'availableIn' => 'https://source.unsplash.com/100x100/?paris||Paris',
'objectID' => 'myID',
],
]);
If you’re updating an existing index, you can use a batch
call of partialUpdateObjects
.
You also need to set the facet attributes as attributesForFaceting
:
1
2
3
4
5
6
$index->setSettings([
'attributesForFaceting' => [
"color",
"availableIn"
]
]);
In the refinementList
widget of your InstantSearch implementation, split your facet values on ||
to display the visual and attribute values.
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
35
36
37
38
39
40
41
// For color facets
search.addWidget(
instantsearch.widgets.refinementList({
container: "#facets",
attribute: "color",
templates: {
item({ label, isRefined }, { html }) {
const [color, name] = label.split("||");
return html`<div>
<input type="checkbox" id="${name}" checked="${isRefined}" />
<label>
${name}
<span class="color" style="${{ "background-color": color }}"></span>
</label>
</div>`;
},
},
}),
);
// For image facets
search.addWidget(
instantsearch.widgets.refinementList({
container: "#facets",
attribute: "availableIn",
templates: {
item({ label, isRefined }, { html }) {
const [image, name] = label.split('||');
return html`
<span
class="location-pair"
style="${isRefined ? "font-weight: bold" : ""}"
>
<img class="location-image" src="${image}" />
<span class="facet-value">${name} (${count})</span>
</span>
`;
},
},
}),
);
Option 2: Adding visual facets with a frontend mapping
You can implement visual facets by adding a mapping directly in your frontend app. This solution requires that you use a consistent naming scheme for your images and facet names.
First, create your mapping functions.
The following function takes the facet label of an image and returns the image with the .jpeg
file extension tacked on. This is a rudimentary implementation: it requires your facets to use consistent image names.
1
const getLocationImage = location => location ? `${location.toLowerCase()}.jpeg` : "";
The following function takes a color facet and returns either the color or “transparent” if nothing was passed. Again, this is a rudimentary implementation: it doesn’t return the hexadecimal color code but instead requires your facets to use valid CSS color keywords.
1
const getHexCodeFromColor = color => color ? color : 'transparent';
In your refinementList
widget, use your mapping functions to transform each facet.
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// For color facets
search.addWidget(
instantsearch.widgets.refinementList({
container: '#facets',
attribute: 'color',
transformItems(items) {
return items.map(item => ({
...item,
hexCode: getHexCodeFromColor(item.label)
}));
},
templates: {
item({ label, hexCode, isRefined }, { html }) {
return html`
<input type="checkbox" id="${label}" checked="${isRefined ? "checked" : ""}" />
<label for="${label}" class="${isRefined ? "isRefined" : ""}">
${label}
<span class="color" style="background-color: ${hexCode}"></span>
</label>
`;
},
},
})
);
// For image facets
search.addWidget(
instantsearch.widgets.refinementList({
container: '#facets',
attribute: 'availableIn',
transformItems(items) {
return items.map(item => ({
...item,
image: getLocationImage(item.label)
}));
},
templates: {
item({ label, image, isRefined }, { html }) {
return html`
<span class="location-pair" style="${isRefined ? "font-weight: bold" : ""}">
<img class="location-image" src="assets/images/locations/${image}" />
<span class="facet-value">${label} (${count})</span>
</span>
`;
},
},
})
);