Custom markup for refinementList

Sorry for what is probably a very basic question – but I am new to Algolia and am trying to understand the best method to render custom markup for the refinementList. I have a basic (and working) understanding of how to achieve some of what I need – but what I am trying to determine is how to go about removing all the OOB markup.

What I get:

<div class="ais-body ais-refinement-list--body">
  <div class="ais-refinement-list--list">
    <div class="ais-refinement-list--item">
      <div>
         ...my custom markup here...

What I am after is basic unordered list markup:

<ul>
    <li>...</li>
</ul>

So I’d like to strip away all the [class*="ais-refinement-list"] and no class list child <div> elements.

I appreciate any help/guidance you might be able to provide. Thank you.

Hi there,
Thanks for reaching out to us.
I’m not sure if you’re using InstantSearch.js or other flavors like React, Vue.js or Angular.
Assuming you’re on InstantSearch.js,


The link above shows you how to customize the markup for refinementList.
At the end of the doc, there is a full example which basically uses ul & li as you said.

I’m happy to help you, so let me know if there is anything you want to know more or if you don’t understand any part of the doc.

Thank you so much. I had looked at this example but wasn’t sure if it would remove those wrapping <div> elements. And yes, you’re correct – using InstantSearch.js – just vanilla JS, no library. I will revisit the example and see if I can work my way through it. Main reason for the markup change = we’re planning on using refinementList with a <button> for the header in order to toggle the associated list of options. For accessibility purposes, it would be best to have the options in a list (screen readers will then announce them as such) – plus we’ll be adding JS to ensure we follow the disclosure pattern for such a component (button uses aria-expanded and aria-controls and both the group of refinementLists and the options within each are navigable via keyboard (arrow up/down/left/right + home & end.) One thing is for certain = Algolia is nothing short of amazing in terms of its capabilities. Thank you again. I may reach out for more help.

Hi @eunjae.lee. I have gotten this far: https://codesandbox.io/s/relaxed-keldysh-vmgek?file=/src/app.js (can you see this example?) – but now am trying to understand why checkboxes don’t show as checked. Left the default implementation of refinementList in place and interestingly enough, the corresponding checkbox in the OOB list get checked when the custom list controls are used. Can you provide some insight as to where I should look to ensure that the checkboxes in the custom refinementList behave same as OOB example? Thank you!

Hey there @bob.prokop! It just looks as though your forgot to take into account whether or not an item was refined when setting the “checked” attribute. See it corrected here: https://codesandbox.io/s/hardcore-shadow-yxsle?file=/src/app.js

<input class="facet__checkbox" id="${item.value}" data-value="${
    item.value
  }" type="checkbox" ${item.isRefined ? 'checked' : ''}> 

Hope this is what you were looking for!

1 Like

@maria.schreiber = Yes! That was it! Thank you SO much!

@maria.schreiber + @eunjae.lee – one more question, please. Is there any reference for the refine() function? As in:

refine(event.currentTarget.dataset.value);

And is it possible to add a callback? Reason: when you check a box and the list of options is refined based on the selection made (or unmade) the form control that was the target of the event loses focus as the DOM is reshuffled. This is an accessibility error – if I am a keyboard user and tab into one of these controls (let’s use the checkbox example) – and I check/uncheck it by pressing the space bar – it should remain focused. The focus appears to move to a top-level parent element (<body> in most instances but in the demo here: https://instantsearchjs.netlify.app/stories/?path=/story/refinements-refinementlist--default – the parent <iframe> receives focus.

I think I can address this using a callback, but ideally it shouldn’t happen.

Perhaps my analysis is completely wrong – but I sure would appreciate some guidance.

Thank you in advance.

Hey! You’re right, the losing of the focus is an accessibility issue that we’re aware of in the refinementList widget. When you use the connectRefinementList connector, you become responsible for the generated DOM, and therefore you need to take care of accessibility features as well.

The browser looses the focus because you use innerHTML at each render, which repaint the whole list DOM element, and therefore the browser cannot track which element was which one after the reorder. This is why frameworks like React, Vue, etc. do some DOM diff computation to reduce DOM changes and have the concept of keys.

The refine method doesn’t accept a callback.

From there I would recommend relying on an actual VDOM (virtual DOM) library (e.g., Preact) that does this diffing for you, otherwise you’ll end up creating your own VDOM library.

@francoischalifour – thank you for the quick response! It seems like the OOB refinementList widget uses the innerHTML method as well – so I guess it is an issue even if you don’t use the connector/custom approach, right? Does that DOM shuffle have to happen on every interaction due to how the selected item(s) move to the top of the list? If so = can that option be disabled? I don’t know that it is a great UX experience anyway – in itself it is kind of an accessibility issue as I’m not certain that screen reader users are made aware of the change and suddenly tabbing (or using arrow keys home/end) are taking you to different options than they just had been doing. Thanks again for the help – it is much appreciated.

@francoischalifour + @maria.schreiber + @eunjae.lee = apparently it is an issue even if you use the refinementList widget OOB; try: https://codesandbox.io/s/bold-tereshkova-mgveu. Try checking the “All Cell Phones with Plans” checkbox under the “CATEGORIES” list. Initially, it is the 8th option in the list (sorted by count); checking it moves it to the top of the list.

Now without doing anything else, use your keyboard and press TAB. It should take you to the next checkbox – in this case, “Cell Phones” – although the <iframe> is not focused (good), the “Smartwatch Accessories” checkbox receives focus. So there must be some logic that is making note of the option checked in order to move focus to the next option – but once the re-sort takes place that logic becomes incorrect and the proper element is natural tab order does not receive focus.

Perhaps there is an option using the OOB refinementList widget to prevent this DOM shuffle in order to maintain proper/natural tab order for keyboard users?

Thanks again for your time.

In the widget implementation, we rely on Preact, which does the DOM diffing for us. The issue comes from the templating engine that we use underneath to allow users to customize the rendering of the widgets: Hogan.js.

The templating system triggers a “hard reset” of the DOM, similar to using innerHTML in the end.

Anyway, you shouldn’t have these problems if you create the DOM properly by yourself using the connector.

@francoischalifour – thank you. Just for final clarification then: if we used the connector method and a library like Preact to monitor UI state we could make this work? Do you know if there is an example of this being done? Standard connector method for refinementList widget here: https://codesandbox.io/s/hardcore-shadow-yxsle?file=/src/app.js – but we need to use some type of virtual DOM lib, right?

Yep, I created an example to show you how it can work using Preact on this sandbox.

1 Like

Thank you @francoischalifour – very helpful :slight_smile: