Say you have a list of 100 names:
<ul>
<li>Randy Hilpert</li>
<li>Peggie Jacobi</li>
<li>Ethelyn Nolan Sr.</li>
<!-- and then some -->
</ul>
…or file names, or phone numbers, or whatever. And you want to filter them client-side, meaning you aren’t making a server-side request to search through data and return results. You just want to type “rand” and have it filter the list to include “Randy Hilpert” and “Danika Randall” because they both have that string of characters in them. Everything else isn’t included in the results.
Let’s look at how we might do that with different technologies.
CSS can sorta do it, with a little help.
CSS can’t select things based on the content they contain, but it can select on attributes and the values of those attributes. So let’s move the names into attributes as well.
<ul>
<li data-name="Randy Hilpert">Randy Hilpert</li>
<li data-name="Peggie Jacobi">Peggie Jacobi</li>
<li data-name="Ethelyn Nolan Sr.">Ethelyn Nolan Sr.</li>
...
</ul>
Now to filter that list for names that contain “rand”, it’s very easy:
li {
display: none;
}
li[data-name*="rand" i] {
display: list-item;
}
Note the i
on Line 4. That means “case insensitive” which is very useful here.
To make this work dynamically with a filter <input>
, we’ll need to get JavaScript involved to not only react to the filter being typed in, but generate CSS that matches what is being searched.
Say we have a <style>
block sitting on the page:
<style id="cssFilter">
/* dynamically generated CSS will be put in here */
</style>
We can watch for changes on our filter input and generate that CSS:
filterElement.addEventListener("input", e => {
let filter = e.target.value;
let css = filter ? `
li {
display: none;
}
li[data-name*="${filter}" i] {
display: list-item;
}
` : ``;
window.cssFilter.innerHTML = css;
});
Note that we’re emptying out the style block when the filter is empty, so all results show.
See the Pen
Filtering Technique: CSS by Chris Coyier (@chriscoyier)
on CodePen.
I’ll admit it’s a smidge weird to leverage CSS for this, but Tim Carry once took it way further if you’re interested in the concept.
jQuery makes it even easier.
Since we need JavaScript anyway, perhaps jQuery is an acceptable tool. There are two notable changes here:
- jQuery can select items based on the content they contain. It has a selector API just for this. We don’t need the extra attribute anymore.
- This keeps all the filtering to a single technology.
We still watch the input for typing, then if we have a filter term, we hide all the list items and reveal the ones that contain our filter term. Otherwise, we reveal them all again:
const listItems = $("li");
$("#filter").on("input", function() {
let filter = $(this).val();
if (filter) {
listItems.hide();
$(`li:contains('${filter}')`).show();
} else {
listItems.show();
}
});
It’s takes more fiddling to make the filter case-insensitive than CSS does, but we can do it by overriding the default method:
jQuery.expr[':'].contains = function(a, i, m) {
return jQuery(a).text().toUpperCase()
.indexOf(m[3].toUpperCase()) >= 0;
};
See the Pen
Filtering Technique: jQuery by Chris Coyier (@chriscoyier)
on CodePen.
React can do it with state and rendering only what it needs.
There is no one-true-way to do this in React, but I would think it’s React-y to keep the list of names as data (like an Array), map over them, and only render what you need. Changes in the input filter the data itself and React re-renders as necessary.
If we have an names = [array, of, names]
, we can filter it pretty easily:
filteredNames = names.filter(name => {
return name.includes(filter);
});
This time, case sensitivity can be done like this:
filteredNames = names.filter(name => {
return name.toUpperCase().includes(filter.toUpperCase());
});
Then we’d do the typical .map()
thing in JSX to loop over our array and output the names.
See the Pen
Filtering Technique: React by Chris Coyier (@chriscoyier)
on CodePen.
I don’t have any particular preference
This isn’t the kind of thing you choose a technology for. You do it in whatever technology you already have. I also don’t think any one approach is particularly heavier than the rest in terms of technical debt.
In the css Section of this you have something like:
Li[data-content… {
}
Could that be used to change the color of certain words in a textarea html tag?
(Code Highlighting)
Nice face-to-face!
But here you’re using JS to generate CSS. => CSS-in-JS (I hate it)
In the CSS example, I would rather use JS to only update a custom property used as the filter in the styles definition (that stays).
It limits the effect to supporting browser I agree, but looks more like everyone is doing its own job by himself.
If what you mean is to parameterize the CSS’ attribute selector with a custom property (i.e.:
li[data-name*=var(--whatever) i] ...
), it is not possible unfortunately.I don’t see anything wrong in metaprogramming a CSS stylesheet and I wouldn’t call it “CSS-in-JS”. A better name could be “JIT CSS”.
If you use the DataList element with a suitable polyfill, you get all of the above in HTML. It’s displayed as textbox suggestions rather than a list, but you also get partial matches in non-Firefox browsers, so that’s cool.
Actually, some methods are heavier than other. Although, it depends on how much data and elements you need to filter.
When building my tool to plan characters in Mass Effect: Andromeda, I first used the JQuery method. For smaller datasets, it worked beautifully. But for weapons, which had 2360 records fully rendered with a relatively big template (many elements), the performance was abysmal.
I then switched to the CSS methods and reduced the filtering time to almost 1/10th of the JQuery method. I was astonished by the results, but after some thoughts it made sense. I was modifying the DOM only once to feed the new CSS filter, compared to hundreds of times with the JQuery method. And browsers are amazingly fast at applying CSS, a lot faster than JS…
Maybe the React method could yield results close to the CSS approach, but I’ve never tested it (not part of my technology stack). Although, I doubt anything can surpass CSS given how optimized it is.
I would use Vanilla JS. This solution should work in any modern browser with the
RegExp
taking care of case-insensitivity:If you still need to support IE11, this works too:
Would be nice to see chained filtering. Say you have an array of objects and that data is displayed to a table format. Then you have a search field and a sort by drop down for example.
Great post Chris! I did not know the āiā in the attribute filter, pretty cool!
Are there any speed tests which compare CSS filtering with JS based filtering?
I actually just used this technique on a project – so you can use it in production.
Oh, and I actually did a pen 6 years ago which uses the Dynamic CSS filtering you mention in your post: https://codepen.io/netsi1964/pen/sInao