A search bar is one of the most common features on any website. So common in fact, that you’d be hard-pressed to find a website that deals with data in one form or another and doesn’t have a search bar. Even the most visited webpage in the world consists almost entirely of a search bar:

A screenshot of the google.com landing pageA screenshot of the google.com landing pageA screenshot of the google.com landing pagegoogle.com

With that being said, in this tutorial we’ll use JavaScript to implement a basic website search bar and search function to filter a list of articles.

1. Markup with HTML

This demo reuses the layout from two previous tutorials. These tutorials also discuss how the data is fetched and displayed and how the styling and layout is setup.

For our updated layout, we’ll be adding a search bar to the header element and an element to hold the text for the search results so this is what the markup looks like:

1
<header>
2
    <div class="search-bar">
3
      <input id="search" type="search" placeholder="&#x1F50D; Start typing to search..." />
4
    </div>
5
</header>
6

7
<main class="container">
8
    <div class="search-display"></div>
9
    
10
    <div class="posts-container"></div>
11
</main>

We use the type="search" to ensure the semantic nature of our input field. It also provides an inbuilt clear search button.

Search field being clearedSearch field being clearedSearch field being cleared
Search field cleared on click

Another feature we can include in our markup is a list of search suggestions that appear in a dropdown when the user clicks on the search input field. This can be achieved using the <datalist> element and list attribute:

1
<div class="search-bar">
2
  <input 
3
    id="search" 
4
    type="search" 
5
    placeholder="Start typing to search..." 
6
    list="search-suggestions" 
7
    autocomplete="off"
8
  />
9
  
10
  <datalist id="search-suggestions">
11
    <option value="JavaScript">
12
    <option value="CSS">
13
    <option value="Accessibility">
14
    <option value="Web Design">
15
  </datalist>
16
</div>
Dropdown of suggestions shown in search inputDropdown of suggestions shown in search inputDropdown of suggestions shown in search input

2. Styling with CSS

This website search demo uses the same styling from the previously listed tutorials with a few additions: we’ve updated the header to be sticky so the search bar is always at top while scrolling, and hidden the default styling for the input list field.

1
header {
2
  position: sticky;
3
  top: -54px; /* value to scroll past the logo so just the search bar is sticky */
4
  z-index: 2;
5
}
6

7
.search-bar {
8
  display: flex;
9
  justify-content: center;
10
  padding: 24px;
11
}
12

13
.search-bar input {
14
  width: 50%;
15
  min-width: 300px;
16
  padding: 12px 24px;
17
  border-radius: 24px;
18
  font-size: 16px;
19
  border: 0px;
20
  outline: none;
21
}
22

23
.search-bar [list]::-webkit-list-button,
24
.search-bar [list]::-webkit-calendar-picker-indicator {
25
  display: none !important;
26
}

3. Website Search Functionality with JavaScript

We’ll be using the Fetch API to retrieve mock data scraped from the Tuts+ authors page and stored in a Github gist and then appending it to the page. The logic behind this is explained in this tutorial How to Filter Data on a Webpage (with JavaScript) and this is what the code looks like:

1
let postsData = "";
2
const postsContainer = document.querySelector(".posts-container");
3

4
fetch(
5
  "https://gist.githubusercontent.com/jemimaabu/564beec0a30dbd7d63a90a153d2bc80b/raw/0b7e25ba0ebee6dbba216cfcfbae72d460a60f26/tutorial-levels"
6
).then(async (response) => {
7
  postsData = await response.json();
8
  postsData.map((post) => createPost(post));
9
});
10

11
const createPost = (postData) => {
12
  const { title, link, image, categories } = postData;
13
  const post = document.createElement("div");
14
  post.className = "post";
15
  post.innerHTML = `
16
      <a class="post-preview" href="${link}" target="_blank">
17
        <img class="post-image" src="${image}">
18
      </a>
19
      <div class="post-content">
20
        <p class="post-title">${title}</p>
21
        <div class="post-tags">
22
          ${categories
23
            .map((category) => {
24
              return '<span class="post-tag">' + category + "</span>";
25
            })
26
            .join("")}
27
        </div>
28
      </div>
29
  `;
30

31
  postsContainer.append(post);
32
};

Now we can get to the search logic.

Input Listener

First, we’ll add an input listener to the search field to detect when text is being typed. When working with event listeners, it’s considered best practice to control how many times the event is called. We can do that using our debounce function.

1
const search = document.getElementById("search");
2

3
let debounceTimer;
4
const debounce = (callback, time) => {
5
  window.clearTimeout(debounceTimer);
6
  debounceTimer = window.setTimeout(callback, time);
7
};
8

9
search.addEventListener(
10
  "input",
11
  (event) => {
12
    const query = event.target.value;
13
    debounce(() => handleSearchPosts(query), 500);
14
  },
15
  false
16
);

In this function, we call the handleSearchPosts function inside our debounce function with a debounce time of 500ms. Next we’ll define the logic inside the handleSearchPosts() function.

Format the Input

When working with user input, it’s a good idea to format the input in some way to prevent unpredictable results. In the case of our search input, we’ll be removing any extra whitespace and also only returning search results for input with more than one character. We’ll also convert the function to lowercase to prevent any case sensitive results. This is what the function looks like so far:

1
const handleSearchPosts = (query) => {
2
  const searchQuery = query.trim().toLowerCase();
3
  
4
  if (searchQuery.length <= 1) {
5
    return
6
  }
7
}

Fetch the Right Data

Next, we want to handle fetching the right data based on the query. We’ll be returning any post where the title or category text contains the query:

1
  let searchResults = [...postsData].filter(
2
    (post) =>
3
      post.categories.some((category) => category.toLowerCase().includes(searchQuery)) ||
4
      post.title.toLowerCase().includes(query)
5
  );

The post categories is an array so we use the some() method to return any matches and the .includes() method to check if the string contains the query . The includes method is case sensitive so we convert the post category and title to lowercase before searching. 

Update Displayed Text

Now we’ve gotten our search results, we can update the search display text to display text based on the results returned:

1
const searchDisplay = document.querySelector(".search-display");
2

3
if (searchResults.length == 0) {
4
    searchDisplay.innerHTML = "No results found"
5
} else if (searchResults.length == 1) {
6
    searchDisplay.innerHTML = `1 result found for your query: ${query}`
7
} else {
8
    searchDisplay.innerHTML = `${searchResults.length} results found for your query: ${query}`
9
}

Append Results

Then we append our search results on the page using the createPost() function. 

1
postsContainer.innerHTML = "";
2
searchResults.map((post) => createPost(post));

We want to be able to reset the page to its default state when the user clears the input field. We can do this by defining a resetPosts() function and calling it in our handleSearchPosts() function

1
const resetPosts = () => {
2
  searchDisplay.innerHTML = ""
3
  postsContainer.innerHTML = "";
4
  postsData.map((post) => createPost(post));
5
};
6

7

8
const handleSearchPosts = (query) => {
9
  const searchQuery = query.trim().toLowerCase();
10
  
11
  if (searchQuery.length <= 1) {
12
    resetPosts()
13
    return
14
  }
15
  
16
  let searchResults = [...postsData].filter(
17
    (post) =>
18
      post.categories.some((category) => category.toLowerCase().includes(searchQuery)) ||
19
      post.title.toLowerCase().includes(searchQuery)
20
  );
21
  
22
  if (searchResults.length == 0) {
23
    searchDisplay.innerHTML = "No results found"
24
  } else if (searchResults.length == 1) {
25
    searchDisplay.innerHTML = `1 result found for your query: ${query}`
26
  } else {
27
    searchDisplay.innerHTML = `${searchResults.length} results found for your query: ${query}`
28
  }
29

30
  postsContainer.innerHTML = "";
31
  searchResults.map((post) => createPost(post));
32
};

And That’s All She Wrote

With this, we have our simple search bar completely set up!

©2024 SIRRONA Media, LLC.  All Rights Reserved.  

Log in with your credentials

Forgot your details?