Quick Search for Webflow

Copy this component ↓

Disclaimers:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.