Customize an Existing Widget
On this page
You are reading the documentation for Vue InstantSearch v2. Read our migration guide to learn how to upgrade from v1 to v2. You can still find the v1 documentation here.
Highlight and snippet your search results
Search is all about helping users understand the results. This is especially true when using text-based search. When a user types a query in the search box, the results must show why the results are matching the query. That’s why Algolia implements a powerful highlight that lets you display the matching parts of text attributes in the results.
Highlighting is based on the results and you will need to make a custom Hit in order to use the Highlighter. The ais-highlight
and the ais-snippet
widgets take two props:
attribute
: the path to the highlighted attribute of the hit (which can be either a string or an array of strings)hit
: a single result object
Notes:
- Use the
ais-highlight
widget when you want to display the regular value of an attribute. - Use the
ais-snippet
widget when you want to display the snippet version of an attribute. To use this widget, the attribute name passed to theattribute
prop must be present in “Attributes to snippet” on the Algolia dashboard or configured asattributesToSnippet
via aset settings
call to the Algolia API.
Here is an example in which we leverage the item slot of the Hit widget. In our results we have a name
field that is highlighted. In these examples we use the mark
tag to highlight. This is a tag specially made for highlighting pieces of text.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<ais-hits :escape-HTML="true">
<div slot="item" slot-scope="{ item }">
<h2>
<ais-highlight
attribute="name"
:hit="item"
highlightedTagName="mark"
/>
</h2>
<p>
<ais-snippet
attribute="description"
:hit="item"
highlightedTagName="mark"
/>
</p>
</div>
</ais-hits>
<ais-configure
:attributesToSnippet="['description']"
snippetEllipsisText="[…]"
/>
Style your widgets
The widgets are shipped with fixed CSS class names.
The format for those class names is ais-NameOfWidget-element--modifier
. We are following the naming convention defined by SUIT CSS.
The different class names used by each widget are described on their respective documentation pages. You can also inspect the underlying DOM and style accordingly.
Loading the theme
We do not load any CSS into your page automatically but we provide two themes that you can load manually:
- reset.css
- algolia.css
We strongly recommend that you use at least reset.css in order to avoid visual side effects caused by the new HTML semantics.
The reset
theme CSS is included within the algolia
CSS, so there is no need to import it separately when you are using the algolia
theme.
Via CDN
The themes are available on jsDelivr:
unminified:
- https://cdn.jsdelivr.net/npm/instantsearch.css@7.3.1/themes/reset.css
- https://cdn.jsdelivr.net/npm/instantsearch.css@7.3.1/themes/algolia.css
minified:
- https://cdn.jsdelivr.net/npm/instantsearch.css@7.3.1/themes/reset-min.css
- https://cdn.jsdelivr.net/npm/instantsearch.css@7.3.1/themes/algolia-min.css
You can either copy paste the content into your own app or use a direct link to jsDelivr:
1
2
3
4
<!-- Include only the reset -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@7.3.1/themes/reset-min.css" integrity="sha256-t2ATOGCtAIZNnzER679jwcFcKYfLlw01gli6F6oszk8=" crossorigin="anonymous">
<!-- or include the full Algolia theme -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@7.3.1/themes/algolia-min.css" integrity="sha256-HB49n/BZjuqiCtQQf49OdZn63XuKFaxcIHWf0HNKte8=" crossorigin="anonymous">
Via Yarn (npm) & Webpack
If you are using Vue CLI, you can import these styles without a separate loader.
1
2
yarn add instantsearch.css
yarn add --dev style-loader css-loader
1
2
3
4
5
6
// App.js
// Include only the reset
import 'instantsearch.css/themes/reset.css';
// or include the full Algolia theme
import 'instantsearch.css/themes/algolia.css';
1
2
3
4
5
6
7
8
9
10
11
12
// webpack.config.js
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
loaders: ['style?insertAt=top', 'css'],
},
],
},
};
Other bundlers
Any other module bundler like Browserify or Parcel can be used to load our CSS. Vue InstantSearch does not rely on any specific module bundler or module loader.
Translate your widgets
Vue InstantSearch doesn’t have a dedicated API for translating text, but it has slots in every component. When there’s data needed for rendering this is provided too. For example in ais-pagination
:
1
2
3
4
5
6
7
8
9
<ais-pagination>
<span slot="first">first</span>
<span slot="previous">prev</span>
<span slot="item" slot-scope="{value, active}" :style="{color: active ? 'red' : 'green'}">
{{value.toLocaleString()}}
</span>
<span slot="next">next</span>
<span slot="last">last</span>
</ais-pagination>
Templating
Vue InstantSearch supports templating via slots. With them you can customize the markup of each sub part of your components. For example in ais-pagination
:
1
2
3
4
5
6
7
8
9
<ais-pagination>
<span slot="first">first</span>
<span slot="previous">prev</span>
<span slot="item" slot-scope="{value, active}" :style="{color: active ? 'red' : 'green'}">
{{value.toLocaleString()}}
</span>
<span slot="next">next</span>
<span slot="last">last</span>
</ais-pagination>
Modify the list of items in widgets
Vue InstantSearch provides two API’s for manipulating lists:
sort-by
, this is the most straightforward API and obviously limited to sortingtransform-items
, this is the most flexible solutions but it requires more involvement on your side
The transform-items
prop is a function that takes the whole list of items as a parameter and it will expect to receive in return another array of items. Most of the examples in this guide will use this API.
Sorting
Using sort-by
1
2
3
4
<ais-refinement-list
attribute="categories"
:sortBy="['name']"
/>
Using transformItems
1
2
3
4
<ais-refinement-list
attribute="categories"
transformItems="items => items.sort((a,b) => a.value.localeCompare(b.value))"
/>
Filtering
In this example we filter out items when the count is lower than 150:
1
2
3
4
<ais-refinement-list
attribute="categories"
transformItems="items => items.filter(item => item.count >= 150)"
/>
Add manual values
By default, the values in a ais-refinement-list
or a ais-menu
are dynamic. This means that the values are updated with the context of the search. Most of the time this is the expected behavior, but in some cases you may want to have a static list of values that never change.
In this example, we use a static list of 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
<template>
<ais-refinement-list
attribute="categories"
:transformItems="getStaticValues"
/>
</template>
<script>
// There is a limitation here with our helper/is.js based libraries
// we do not provide values that were refined and that have 0 count
// In this case, this is problematic because a correct implementation should
// keep track of the selected values and implementing in this situation would
// be a lot of code.
export default {
methods: {
getStaticValues: (items) => {
const staticValues = ['Cell Phones', 'Unlocked Cell Phones'];
return staticValues.map(value => {
const item = items.find(item => item.label === value);
return item || {
label: value,
value,
count: 0,
isRefined: false,
highlighted: value,
};
});
}
}
};
</script>
Searching long lists
Use the searchable
prop to add a search box to supported widgets:
1
2
3
4
<ais-refinement-list
attribute="categories"
searchable
/>
Apply default value to widgets
A question that comes up frequently is “how do I instantiate a ais-refinement-list
widget with a pre-selected item?”. For this use case, you can use the ais-configure
widget.
The following example instantiates a search page with a default query of “apple” and will show a category menu where the item “Cell Phones” is already selected:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<ais-instant-search
index-name="instant_search"
:search-client="searchClient"
>
<ais-configure
query="apple"
:disjunctive-facets-refinements.camel="{ categories: ['Cell Phones'] }"
/>
<ais-refinement-list attribute="categories"/>
<ais-search-box />
<ais-hits />
</ais-instant-search>
</template>
<!-- You need to instantiate `searchClient` in your script -->
How to provide search parameters
Algolia has a wide range of parameters. If one of the parameters you want to use is not covered by any widget, then you should use the ais-configure
widget.
Here’s an example configuring the number of results per page:
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<ais-instant-search
index-name="your_indexName"
:search-client="searchClient"
>
<ais-configure :hits-per-page.camel="5" />
<ais-search-box />
<ais-hits />
</ais-instant-search>
</template>
<!-- You need to instantiate `searchClient` in your script -->
Dynamic update of search parameters
Updating the props of the ais-configure
widget will dynamically change the search parameters and will trigger a new search.
Filter your results in a way that is not covered by any widget
Our widgets already provide a lot of different ways to filter your results but sometimes you might have more complicated use cases that require the usage of the filters search parameter.
Don’t use filters on a attribute already used with a widget, it will conflict.
1
<ais-configure :filters="NOT categories:\"Cell Phones\""></ais-configure>
Customize the complete UI of the widgets
Extending Vue InstantSearch widgets is the second layer of our API. Read about the two others possibilities in the “What is InstantSearch?” guide.
Unlike other flavors of InstantSearch, Vue InstantSearch does not provide connectors per se. Instead, each widget provides a top level slot to allow for complete customization while being able to reuse the logic of the widget.
When do I need to extend widgets?
By extending widgets we mean being able to redefine the rendering output of an existing widget. Let’s say you want to render the Menu widget as an HTML select
element. To do this you need to extend the Menu widget.
Here are some common examples that require the usage of the connectors API:
- When you want to display our widgets using another UI library
- When you want to have full control on the rendering without having to reimplement business logic
- As soon as you hit a feature wall using our default widgets
How widgets are built
Vue InstantSearch widgets are built in two parts:
- business logic code
- rendering code
The business logic is what we call connectors
. Those connectors are provided by InstantSearch.js and their interfaces are exposed through a scope on the default slot.
Connectors render API
We try to share as much of a common API between all connectors. So that once you know how to use one connector, you can use them all.
Slot scopes
Most of the connectors will use the same naming for properties passed down to the slot.
items[]
: array of items to display, for example the brands list of a custom Refinement List. Every extended widget displaying a list gets an items property to the data passed to its render function.refine(value|item.value)
: will refine the current state of the widget. Examples include: updating the query for a custom SearchBox or selecting a new item in a custom RefinementList.currentRefinement
: currently applied refinement value (usually the call value of refine()).createURL(value|item.value)
: will return a full url you can display for the specific refine value given you are using the routing feature.
An item is an object that you will find in items
arrays. The shape of those objects is always the same.
item.value
: The underlying precomputed state value to pass torefine
orcreateURL
item.label
: The label to display, for example “Samsung”item.count
: The number of hits matching this itemitem.isRefined
: Is the item currently selected as a filter
Some connectors will have more data than others. Read their API reference to know more. Connectors for every widget are documented in the API reference, for example the ais-menu
widget.
Extending widget example
If you want to override complete rendering, that is done with the default
slot of every widget. Here’s an example of that for ais-menu
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<ais-menu attribute="categories">
<ol slot-scope="{ items, createURL, refine }">
<li
v-for="item in items"
:key="item.value"
>
<component :is="item.isRefined ? 'strong' : 'span'">
<a
:href="createURL(item.value)"
@click.prevent="refine(item.value)"
>
{{item.label}} - {{item.count}}
</a>
</component>
</li>
</ol>
</ais-menu>
Below is a fully working example of a Menu widget rendered as a select
HTML element.
The files to look at are src/components/MenuSelect.vue where you will find the code using the default slots to extend the menu widget.
Head over to our community forum if you still have questions about extending widgets.