In-Page Filtered Search With Vanilla JavaScript

Avatar of Hilman Ramadhan
Hilman Ramadhan on

If you have a page that includes a lot of information, it’s a good idea to let users search for what they might be looking for. I’m not talking about searching a database or even searching JSON data — I’m talking about literally searching text on a single rendered web page. Users can already use the built-in browser search for this, but we can augment that by offering our own search functionality that filters down the page making matching results easier to find and read.

Here’s a live demo of what we’re going to build:

I use this same technique on my real project: https://freestuff.dev/.

Meet JavaScript!

Well, you might know JavaScript already. JavaScript is going to handle all the interactivity in this journey. It’s going to…

  • find all the content we want to search through,
  • watch what a user types in the search input,
  • filter the innerText of the searchable elements,
  • test if the text includes the search term (.includes() is the heavy lifter here!), and
  • toggle the visibility of the (parent) elements, depending on if they include the search term or not.

Alright, we have our requirements! Let’s start working.

The basic markup

Let’s assume we have a FAQ page. Each question is a “card” which has a title and content:

<h1>FAQ Section</h1>

<div class="cards">
  <h3>Who are we</h3>
  <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized </p>
</div>

<div class="cards">
  <h3>What we do</h3>
  <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized </p>
</div>

<div class="cards">
  <h3>Why work here</h3>
  <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized</p>
</div>

<div class="cards">
  <h3>Learn more</h3>
  <p>Want to learn more about us?</p>
</div>

Imagine there are a lot of questions on this page.

To get ready for the interactivity, we’ll have this one line of CSS. This gives us a class we can add/remove depending on the search situation when we get to the JavaScript:

.is-hidden { display: none; }

Let’s add a search input with an event that fires when it is interacted with:

<label for="searchbox">Search</label>
<input 
  type="search" 
  oninput="liveSearch()" 
  id="searchbox" 
>

The JavaScript baseline

And here’s the JavaScript that does everything else!

function liveSearch() {
  // Locate the card elements
  let cards = document.querySelectorAll('.cards')
  // Locate the search input
  let search_query = document.getElementById("searchbox").value;
  // Loop through the cards
  for (var i = 0; i < cards.length; i++) {
    // If the text is within the card...
    if(cards[i].innerText.toLowerCase()
      // ...and the text matches the search query...
      .includes(search_query.toLowerCase())) {
        // ...remove the `.is-hidden` class.
        cards[i].classList.remove("is-hidden");
    } else {
      // Otherwise, add the class.
      cards[i].classList.add("is-hidden");
    }
  }
}

You can probably go line-by-line there and reason out what it is doing. It finds all the cards and the input and saves references to them. When a search event fires, it loops through all the cards, determines if the text is within the card or not. It the text in the card matches the search query, the .is-hidden class is removed to show the card; if not, the class is there and the card remains hidden.

Here is the link to the demo again.

Adding a delay

To make sure our JavaScript doesn’t run too much (meaning it would slow down the page), we will run our liveSearch function only after waiting an “X” number of seconds.

<!-- Delete on Input event on this input -->
<label for="searchbox">Search</label>
<input type="search" id="searchbox">
// A little delay
let typingTimer;        
let typeInterval = 500; // Half a second
let searchInput = document.getElementById('searchbox');

searchInput.addEventListener('keyup', () => {
  clearTimeout(typingTimer);
  typingTimer = setTimeout(liveSearch, typeInterval);
});

What about fuzzy searches?

Let’s say you want to search by text that is not visible to user. The idea is sort of like a fuzzy search, where related keywords return the same result as an exact match. This helps expand the number of cards that might “match” a search query.

There are two ways to do this. The first is using a hidden element, like a span, that contains keywords:

<div class="cards">
  <h3>Who are we</h3>
  <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized</p>
  
    <!-- Put any keywords here -->
   <span class="is-hidden">secret</span> 
</div>

Then we need to update our liveSearch function. Instead of using .innerText we will use .textContent to includes hidden elements. See more detail about the difference between innerText and textContent here

 for (var i = 0; i < cards.length; i++) {
        if(cards[i].textContent.toLowerCase()
                .includes(search_query.toLowerCase())) {
            cards[i].classList.remove("is-hidden");
        } else {
            cards[i].classList.add("is-hidden");
        }
    }

Try typing “secret” on a search box, it should reveal this card, even though “secret” isn’t a displayed on the page.

A second approach is searching through an attribute. Let’s say we have a gallery of images. We can put the keywords directly on the alt attribute of the image. Try typing “kitten” or “human” in the next demo. Those queries are matching what’s contained in the image alt text.

For this to work, we need to change innerText to getAttribute('alt') since we want to look through alt attributes in addition to what’s actually visible on the page.

for (var i = 0; i < cards.length; i++) {
  if(cards[i].getAttribute('alt').toLowerCase()
    .includes(search_query.toLowerCase())) {
      cards[i].classList.remove("is-hidden");
  } else {
    cards[i].classList.add("is-hidden");
  }
}

Depending on your needs, you could put your keywords in another attribute, or perhaps a custom one.

Caveat

Again, this isn’t a search technology that works by querying a database or other data source. It works only if you have all the searchable content in the DOM on that page, already rendered.

So, yeah, there’s that. Just something to keep in mind.

Wrapping up

Obviously, I really like this technique, enough to use it on a production site. But how else might you use something like this? An FAQ page is a clear candidate, as we saw, but any situation that calls for filtering any sort of content is fit for this sort of thing. Even a gallery of images could work, using the hidden input trick to search through the alt tag content of the images.

Whatever the case, I hope you find this helpful. I was surprised that we can get a decently robust search solution with a few lines of vanilla JavaScript.

Have you used this technique before, or something like it? What was your use case?