Here's a solid solution for collapsing an Algolia InstantSearch (AIS) refinement list in Vue without "resetting" the selected filters.
Problem Recap
- Hiding or not rendering
<ais-refinement-list>makes AIS forget user-selected filters. - We want to let users collapse (hide the choices) but keep their selected facets.
Solution Approach
Key Point:
Only hide the visible part (using CSS), never remove/ref destructure the component from the DOM (i.e., avoid v-if or v-show on <ais-refinement-list> directly).
This way, AIS keeps facet selections in its state.
Implementation Example
1. Always render <ais-refinement-list>.
2. Use CSS or Vue bindings to collapse the content area inside the refinement list.
Here's a sample approach (adjust for your setup as needed):
<h3
class="text-sm font-medium my-2 px-3 text-dark-blue pb-1 border-b cursor-pointer select-none flex justify-between items-center"
@click="showColorFamily = !showColorFamily"
>
<span>Color Family</span>
<span class="text-xs">
{{ showColorFamily ? '−' : '+' }}
</span>
</h3>
<ais-refinement-list
:class-names="{
'ais-RefinementList-list': 'flex flex-col gap-0',
'ais-RefinementList-item': 'flex items-center justify-between px-2 py-0.5',
'ais-RefinementList-label': 'flex items-center gap-2 w-full',
'ais-RefinementList-checkbox': 'w-4 h-4 accent-dark-blue',
'ais-RefinementList-labelText': 'truncate',
'ais-RefinementList-count': 'text-xs text-gray-500',
'ais-RefinementList-item--selected': 'font-semibold',
'ais-RefinementList-item--disabled': 'opacity-50 cursor-not-allowed'
}"
:limit="20"
:sort-by="['name:asc']"
attribute="color_family"
>
<template #default="{ items, refine }">
<ul :style="{ display: showColorFamily ? '' : 'none' }">
<li v-for="item in items" :key="item.value"
:class="[
'flex items-center gap-2 px-2 py-0.5 rounded flex-1',
item.isRefined ? 'font-semibold' : ''
]"
>
<img
:src="colorFamilyImages[item.value] ?? '/storage/images/icons/colors/placeholder.png'"
alt=""
class="w-8 h-8 object-contain"
/>
<label class="flex items-center gap-2 cursor-pointer">
<input
:checked="item.isRefined"
:disabled="item.isDisabled"
class="w-4 h-4 accent-dark-blue"
type="checkbox"
@change="refine(item.value)"
/>
<span class="truncate">{{ item.label }}</span>
</label>
<span class="text-xs text-gray-500 ml-auto">{{ item.count }}</span>
</li>
</ul>
</template>
</ais-refinement-list>
Key changes:
- The
<ais-refinement-list>is never removed. - The internal
<ul>...</ul>is hidden via:style="{ display: showColorFamily ? '' : 'none' }"or you can do this via a CSS class. - You can keep the header, selected items, etc. always visible if you prefer.
Optional: Animate the Collapse
You can use Vue <transition> wrappers and CSS classes for a nice collapse animation, but always make sure the underlying <ais-refinement-list> stays mounted.
Summary
- Never conditionally render the
<ais-refinement-list>itself (v-iforv-show--> will break facet state). - Collapse only the visible DOM inside using regular Vue show/hide techniques on an inner element.
- This keeps AIS state & selections "alive".
If you need help with animating or customizing the collapse further, let me know!