Quick Search for Webflow
Disclaimers:
- This demo only works for projects that have site search enabled, aka Business Plan or higher. Otherwise, you won't be able to use this.
- Being that Webflow requires a Business Plan or higher, the search results page & styling can't be included in this demo. However, I will walk you through the steps to use on your own search results page.
- This demo uses a good amount of custom code. Use at your own risk or reach out to a Javascript developer for additional help if you get stuck.
- This demo uses an attributes-based method — this is used as a safeguard in case you frequently change your class names. If you do, the code won't break. Set it & forget it.
- There are likely a lot of edge cases for a solution like this. This is not intended to cover everything, but simply provide a foundation for basic quick-search.
Summary:
This demo will allow you to create a Quick Search widget using the native Webflow search results page. Not only will you be able to create Quick Results, but also filtered Quick Results. This can be useful in situations where you need to have multiple search components that return different results, such as for a site that has a Blog & Help Center.
Most importantly, this will help you better deliver powerful search to clients. As of now, one of the most commonly used search widgets for Webflow projects is Swiftype, which is expensive. This demo works with the native Webflow search & nothing more.
Steps:
1) Copy the search component into your project
This can go wherever you want, just be sure to copy the element wrapper that contains [COPY-THIS].
2) Copy the following Javascript code (from the live site)
Paste it into the Footer code section of your site-wide settings.
<script>
window.addEventListener("DOMContentLoaded", () => {
const searchComponent = document.querySelector("[data-search-component]");
const searchInput = document.querySelector("[data-search-input]");
const searchCollectionSlug = searchComponent.getAttribute(
"data-search-collection-slug"
);
const searchResultsContainer = document.querySelector(
"[data-search-results-container]"
);
const searchSpinner = document.querySelector("[data-search-spinner]");
let searchResultsList;
let timer;
// check for existence of search component on page
// if it does exist, run the code inside
if (searchComponent) {
// create a function to load the search results
const loadSearchResults = () => {
// show the search spinner immediately when the function is called
searchSpinner.style.display = "flex";
// if any previous HTML was added to the search results container, remove it
searchResultsContainer.innerHTML = "";
// fetch the HTML from the search results page using the input text as the query parameter
fetch(`${document.location.origin}/search?query=${searchInput.value}`)
.then((result) => {
return result.text();
})
.then((searchPage) => {
// parse the text to HTML
const parser = new DOMParser();
const searchPageHTML = parser.parseFromString(
searchPage,
"text/html"
);
searchResultsList = searchPageHTML.querySelector(
"[data-search-results]"
);
// add the search results inside of the search results container
searchResultsContainer.innerHTML += searchResultsList.innerHTML;
// OPTIONAL — use this if you'd like to filter your results to specific collection(s)
// ------ Start of optional code ------ //
// create an array from the collections to be included
const collectionsToSearch = searchCollectionSlug
.replace(/\s+/g, "")
.split(",")
.map((value) => {
return "/" + value + "/";
});
// filter out collections that don't match
Array.from(searchResultsContainer.querySelectorAll("a")).forEach(
(link) => {
const isMatch = collectionsToSearch.some((collectionSlug) => {
if (link.getAttribute("href").includes(collectionSlug)) {
return true;
}
return false;
});
if (!isMatch) {
link.parentElement.remove();
}
}
);
// ------ END of optional code ------ //
})
.then(() => {
searchResultsContainer.style.display = "block";
searchSpinner.style.display = "none";
})
.catch(() => {
searchSpinner.style.display = "none";
});
};
// on each keystroke, set a delay on the loadSearchResults() function
searchInput.addEventListener("keyup", () => {
clearTimeout(timer);
timer = setTimeout(() => {
if (searchInput.value.length === 0) {
searchResultsContainer.style.display = "none";
return;
}
loadSearchResults();
}, 500);
});
// reset the timer when the user types
searchInput.addEventListener("keydown", () => {
clearTimeout(timer);
});
// when the search input is focused, show the search results container
searchInput.addEventListener("focusin", () => {
if (searchInput.value.length) {
searchResultsContainer.style.display = "block";
}
});
// when the search input is not focused, hide the search results container
searchInput.addEventListener("focusout", () => {
setTimeout(() => {
searchResultsContainer.style.display = "none";
}, 100);
});
}
});
</script>
3) Copy the following CSS code (from the live site)
Paste it into the Header code section of your site-wide settings.
<style>
[data-search-results-container] {
height: fit-content;
}
[data-search-results-text] {
text-overflow: ellipsis;
}
[data-search-spinner] {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
4) Locate your search component's wrapper element
This element has an attribute data-search-collection-slug. If you do NOT want to filter your search results by collections, remove this attribute completely & remove the following code from the snippet:
// ------ Start of optional code ------ // // create an array from the collections to be included const collectionsToSearch = searchCollectionSlug .replace(/\s+/g, "") .split(",") .map((value) => { return "/" + value + "/"; }); // filter out collections that don't match Array.from(searchResultsContainer.querySelectorAll("a")).forEach( (link) => { const isMatch = collectionsToSearch.some((collectionSlug) => { if (link.getAttribute("href").includes(collectionSlug)) { return true; } return false; }); if (!isMatch) { link.parentElement.remove(); } } ); // ------ END of optional code ------ //
If you wish to filter by specific collections, change the attribute value from "your-collection-slug-here" to the slugs of your collections, separated my commas. If you only want to include 1 collection, include just that slug with no commas.
Ex. data-search-collection-slug="articles, faqs" OR... data-search-collection-slug="articles".
5) Enable site search inside of your project if not already enabled
Once enabled, navigate to the Search Results page & locate the Search Results Wrapper. Add the following attribute to it: data-search-results="true".
6) For each text element inside of each search results item, add the following attribute
data-search-results-text="true"
This will prevent the text from having the wrap. Also, be sure to set the text element to overflow:hidden.
7) For each search results item, add a link block with whatever content you want inside
This can be text, images, anything. Style accordingly. What you see visually is EXACTLY what you will see inside of the quick-search results, so make it look sharp!
8) Style the empty state
If no results are found, the quick-search results will show the empty state container.
9) Style the rest of your search component accordingly
Aside from interactions, you have strong control over how to style the quick-search widget.
Note: For the best experience, be sure to set the spinner container & search results container to display:none before publishing.