← All posts

HTML5 Data Attributes Search using jQuery

· JavaScript, HTML

First appeared on mobifreaks.com · Updated

I had to search a list of names, entirely on the client and quickly. I first tried Filtrify, an advanced tag filtering plugin built on HTML5 data attributes. It was good, but it generated a dropdown for picking movie genres, and my list wasn't about movies. I wanted a plain text field that hides every item that doesn't match. So I started digging.

The 2012 version

<input type="text" id="input" onfocus="this.value = '';" value="Start Searching">
<ul>
  <li data-label="ant">ant</li>
  <li data-label="apple">apple</li>
  <li data-label="ball">ball</li>
  <!-- ...45 words in total -->
</ul>
jQuery(document).ready(function ($) {
  $('#input').on('keyup', function () {
    var input = $(this).val();
    $('ul li').show();
    $('ul li').not('[data-label*=' + input + ']').hide();
  });

On every key press it showed all items, then hid the ones whose data-label didn't contain the typed text.

What was wrong with it

  • The closing }); was missing, so the snippet as published didn't run.
  • It was case-sensitive. I listed this as a known issue: typing "a" wouldn't match "Ant".
  • It broke on spaces and quotes. The typed text went straight into the selector without quotes, so typing two words or an apostrophe caused a selector error.
  • The placeholder was a fake value, cleared on focus. Screen readers read "Start Searching" as if it were typed text, and the field had no label.
  • keyup missed pasting with the mouse.

The same thing today

<label for="filter">Filter words</label>
<input type="search" id="filter" placeholder="Start typing" autocomplete="off">
<p id="filter-status" aria-live="polite"></p>

<ul id="words">
  <li data-label="ant">ant</li>
  <li data-label="apple">apple</li>
  <li data-label="ball">ball</li>
</ul>
const input = document.getElementById('filter');
const items = [...document.querySelectorAll('#words li')];
const status = document.getElementById('filter-status');

input.addEventListener('input', () => {
  const query = input.value.trim().toLowerCase();
  let shown = 0;
  for (const item of items) {
    const match = item.dataset.label.toLowerCase().includes(query);
    item.hidden = !match;
    if (match) shown++;
  }
  status.textContent = query ? `${shown} of ${items.length} shown` : '';
});
Live demo. Open in a new tab
  • No jQuery. dataset, includes() and the hidden property do the work.
  • Case-insensitive, and safe with any characters, because nothing is built into a selector.
  • The input event fires for typing, pasting and clearing the field.
  • A real label and placeholder, and a live status message so screen reader users hear how many items match.

For a few hundred items this is instant. For thousands, or for fuzzy matching ("aple" finding "apple"), a small library like Fuse.js is worth it.