How to style a datalist and its options?

Style datalist options - Open graph

If you ever tried to change the look and feel of a HTML5 datalist but couldn’t, then this post might be your (delayed) Christmas present 🎄🎁 (I hope you had a great one and close to the ones you love the most)!

What’s the problem of styling a HTML5 datalist?

At the moment of writing, it’s not possible to style a datalist directly.

Hopefully in the future, we will be able to simply use normal CSS like this:

.mr-datalist > * {
    background-color: var(--dark);
    color: var(--light);
}

For now, if you still want to use datalist, there are two ways to do it:

The CSS code above will work after implementing any of those 2 options.

Some things that might be important to you: 

  • These methods might not work on iOS versions prior to the 13th. On those devices, the datalist will not change, working as it does by default.
  • Both methods should be future-proof. When HTML5 data lists become easier to style, you can remove the front-end toolkit or the JavaScript code. Just keep the classes and the CSS as is.

With that aside, let’s move on with this tutorial…

The easiest way to style a datalist

You can use a front-end toolkit such as Mr.Utils.

All you have to do is, make sure you are adding the global JavaScript file js/utils.js in your page (or if you don’t want to load the entire toolkit, you can individually add dev/js/utils/utils_components.js).

After that, you need to add the correct class .mr-datalist to your datalist (not to the input) and it will magically transform it into a “search” component.

You can then style it with a CSS code similar to the one shown before.

Style a datalist with code

I mentioned that the easiest way of implementation works like magic, but of course there’s no magic behind it at all, just the correct JavaScript code.

In this post, I’ll share a simplified version of that code with you.

You can either follow the steps or jump directly to the Gist full code.

Feel free to change it to your specific needs.

1- Transform the datalist into an element that can be styled.

document.addEventListener("DOMContentLoaded", function () {
  const mrDataLists = document.querySelectorAll(".mr-datalist");
  for (let id = 0; id < mrDataLists.length; id++) {
    let mrDataList = mrDataLists[id];
    let mrDataListUL = "";
    let mrDataListClone = "";
 
    mrDataListClone =
      mrDataList
        .cloneNode(true)
        .innerHTML.replaceAll("<option", "<li")
        .replaceAll("</option>", "</li>");
    mrDataListUL = document.createElement("ul");
    mrDataListUL.id = mrDataList.id;
    mrDataListUL.className = mrDataList.className;
    mrDataListUL.innerHTML = mrDataListClone;
 
    mrDataList.replaceWith(mrDataListUL);
  }
}

In this first step, we grab all data lists that have the class .mr-datalist, but you can change it to your own.

You can even grab all data lists without exception, by replacing the class .mr-datalist with the property datalist, on the querySelectorAll (Don’t forget to change the CSS code accordingly at the end).

In this example, we transform the datalist into an unordered list. You can change that if you wish, but it should be an element with direct children. Each direct child will be a datalist option.

The replaceWith at the end finishes the transformation, but we are not done yet.

We need to create a similar “search” functionality to the one the HTML5 datalist has.

2- Make the input find content on the element.

document.addEventListener("keyup", function (e) {
  let mrDataListID = "mr-datalist";
  if (e.target.matches('input[list="'+mrDataListID+'"]')) {
    mrSearch(document.getElementById(mrDataListID), e.target);
  }
  e.stopPropagation();
});

In here, I’m assuming we know the ID of the datalist, and that we have it on the input “list” attribute. I’m assuming that because those are needed for the original datalist to work.
In this example, the ID is mr-datalist, set on the mrDataListID variable.

If for some reason you don’t know the ID, there are alternative ways to do it. On Mr.Utils for example, the “Search” component removes the original input, creates a new one and uses nextElementSibling. It does that for a reason, you can check that on the GitHub repository.

As you might notice, we are calling the function mrSearch, which will do the heavy work. 

Here is the function simplified, feel free to rename it to anything you want:

function mrSearch(t, e) {
  //t = Datalist
  //e = Input with list attribute
  let mrSearchChildren = t.children;
  if (mrSearchChildren) {
    if (e.value !== "") {
      t.style.removeProperty("display");
      for (let id = 0; id < mrSearchChildren.length; id++) {
        let mrSearchChild = mrSearchChildren[id];
        if (mrSearchChild.hasAttribute("value")) {
          //If the child does not contain any text, show the value as text.
          if (!mrSearchChild.innerText) {
            mrSearchChild.innerText = mrSearchChild.getAttribute("value");
          }
 
          //Change the input value when clicking on the child.
          mrSearchChild.addEventListener("click", function () {
            e.value = mrSearchChild.getAttribute("value");
          });
        }
        mrSearchChild.style.display = "none";
 
        //Checking the child outerHTML because I want to search text also inside the value attribute. Convert in lowercase to not be case sensitive.
        if (
          mrSearchChild.outerHTML
            .toLowerCase()
            .replace(/[^a-zA-Z0-9 ]/g, "")
            .includes(e.value.toLowerCase().replace(/[^a-zA-Z0-9 ]/g, ""))
        ) {
          mrSearchChild.style.removeProperty("display");
        }
      }
    } else {
      t.style.display = "none";
    }
  }
}

Here we changed the Input value when clicking on the element child (the datalist option that we transformed) and we’ll make the search case-insensitive.

Check the comments inside the code to better understand which parts replicate which datalist “search” feature.

Extra: If you have full control over the original datalist, you probably added a value and text to each option. If you don’t have, then this code as you covered, it copies the value into the text when no text is found.

And that’s it! Now you replicated most datalists’ functionalities and can start styling the element, with a CSS code similar to the one shown before.

3- Take it to the next level (Optional and Advanced)

If you feel inspired, now that you have full control over the code, you can add extra features! Features that not even the original datalist has.

For example:

  • Show a message when no result is found.
  • Bold the text while it is found on the results.
  • If no exact match is found, search by each keyword instead (You can also show a message in that situation).
  • Show results only after a minimum number of characters (This is a good idea to avoid connection words and code properties).

I did many of those features for Mr.Utils, you can check the part of the code here.

Might also be a good idea, to track pressing keyboard arrow keys, to select the results and improve accessibility.

Gist full code on how to style a datalist

Working example of styling datalists

I did something very similar to the methods explained above for Rydoo’s Compliance Centre and Marketplace.

I hope you enjoyed this post! I’m not sure that I will be writing something more on the blog before entering the new year, so enjoy the celebrations and let’s enter 2023 with the right footer!! 🥂🎆🥳

Comments