enhancing interactive search with taxonomy filters
Making Search Smarter with Taxonomies
As your knowledge base grows, so does the challenge of helping users find exactly what they need. Even with full-text search, users can become overwhelmed by too many results. This is where taxonomy filters—like tags, categories, and custom metadata—come into play. By integrating these into your search UI, you provide a way to narrow down results interactively.
Why Use Filters in Jekyll
- Improve usability and speed of discovery
- Reduce irrelevant search results
- Group content around common themes or attributes
In this guide, we'll build dynamic filters for your client-side search using JavaScript and structured front matter in your Jekyll collections.
Structuring Metadata in Your Jekyll Collections
Let’s assume your knowledge base has two content types: guides and FAQs. Each document includes taxonomies like category, tags, and product:
---
title: "Resetting your password"
category: "account"
tags: ["security", "login"]
product: "core"
---
This metadata will be included in your JSON index for client-side filtering.
Updating the JSON Search Index with Filters
Let’s modify your search.json template to output taxonomies:
[
{% assign docs = site.guides | concat: site.faqs %}
{% for doc in docs %}
{
"title": "{{ doc.title | escape }}",
"url": "{{ doc.url | relative_url }}",
"collection": "{{ doc.collection }}",
"category": "{{ doc.category }}",
"tags": {{ doc.tags | jsonify }},
"product": "{{ doc.product }}",
"content": {{ doc.content | strip_html | strip_newlines | jsonify }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
Now your JavaScript will have access to this metadata and can group or filter documents accordingly.
Building the Filter UI in HTML
Create a basic interface to allow users to select filters:
Optionally, create a tag cloud or checkbox list if you're dealing with tags:
Filtering Search Results with JavaScript
Once your index is loaded, combine text search and filter logic like this:
function applyFilters(results, documents) {
const category = document.getElementById("category-filter").value;
const product = document.getElementById("product-filter").value;
const tags = Array.from(document.querySelectorAll("#tags-filter input:checked")).map(el => el.value);
return results.filter(result => {
const doc = documents.find(d => d.url === result.ref);
return (!category || doc.category === category) &&
(!product || doc.product === product) &&
(tags.length === 0 || tags.every(t => doc.tags.includes(t)));
});
}
Call this filter logic after Lunr returns its raw matches:
const filtered = applyFilters(results, documents);
Refreshing the UI Dynamically
Wire up your filter inputs to re-run the search and update the display:
document.querySelectorAll("#filters select, #filters input").forEach(input => {
input.addEventListener("change", performSearch);
});
This keeps the interface responsive and dynamic as users interact with it.
Pre-Building Filter Metadata Lists
If you want filters to be auto-generated rather than hardcoded, create a data file or use Liquid to gather all values at build time:
{% assign all_categories = site.guides | map: "category" | uniq %}
{% for cat in all_categories %}
<option value="{{ cat }}">{{ cat | capitalize }}</option>
{% endfor %}
This can be done for tags, products, or any other field. Keep in mind that since it's static, the options reflect only what's present at build time.
Performance Considerations
- Don’t bloat your index with overly detailed metadata
- Use
data-*attributes on results to improve filtering speed - Use debounce on input listeners to reduce lag
You can also consider paginating long results to improve UI responsiveness.
Adding URL Parameters for Shareable Filters
To make filtered states shareable, serialize them to the URL:
const queryParams = new URLSearchParams();
queryParams.set("category", category);
queryParams.set("tags", tags.join(","));
history.pushState({}, "", "?" + queryParams.toString());
Then on load, parse the URL and apply those settings back to the UI and search engine.
Combining Filters with Multilingual Indexes
If you followed the multilingual setup from the previous article, filters still work—just apply them to the currently loaded index. You'll need to rebuild each index with taxonomies per language.
Conclusion
Faceted search via taxonomy filters is a powerful way to enhance your Jekyll-based knowledge base. By structuring metadata in front matter and combining it with client-side logic, you allow users to slice through your content faster and more precisely. This approach scales well, is SEO-friendly, and works entirely on GitHub Pages without server-side code.
Next up, we’ll explore integrating autocomplete and smart suggestions to help users get instant answers as they type.