CSS 🀝 HTML Data Attributes

alserembani

Atif Aiman

Posted on June 9, 2023

CSS 🀝 HTML Data Attributes

Salam and hello everyone!

Whoa! It has been a long time, since I wrote the last article here, but here we are! This time, I will write about things that "I believe" some of the developers didn't know that you don't have to do conditions for classes in HTML!

Pikachu shocked developers are still using conditions to style HTML

To explain further, let us go deeper on what is the problem that I attempt to highlight here.


Style States For A HTML Component

Let me bring a use case here. Just imagine that you have tabs, and you need to track the state whether the tab is active or not. Of course, one way of doing it is using Javascript!



<nav id="tab">
  <a href="#" />
  <a href="#" />
  <a href="#" />
</nav>

<script>
  const activeTab = 0;

  const navTab = document.getElementById("tab");
  const targetTab = navTab.children[activeTab];

  targetTab.className.add("tab-active");
</script>


Enter fullscreen mode Exit fullscreen mode

For a vanilla Javascript, it makes sense that you can use className.add() to add the tab-active class, right?

So, let's head to the CSS part!



#tab a {
  // Initial state of tab
}

#tab a.tab-active {
  // Active state of tab
}


Enter fullscreen mode Exit fullscreen mode

That's cool! With the change of activeTab value, we will trigger the tab-active based on that. Keep in mind that you need to remove the class if it is no longer active!

This will be one way of solving the conditional issue with vanilla code. Let's jump to one of the framework, and we will try to replicate this thing, shall we? And of course, I will choose React!


Things Are Going To Get Tough

By using React, stuffs are getting easier. But are we? Let us try to replicate a simple way of doing the same thing I previously attempted to do.



const Tab = (tabList) => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <nav id="tab">
      {
        tabList.map((tabConfig, index) => (
          <a href={tabConfig.href} className={activeTab === index ? 'tab-active' : ''} />
        )
      }
    </nav>
  );
};


Enter fullscreen mode Exit fullscreen mode

Nice! Except, that it is unnecessary to set the className to an empty string if it is not active, right? This might affect the readability as the component grows!

So, let me introduce to you, the data-attributes!

Some of you might heard about data-attributes, and you might be interested in this!

A man flexing his data-attribute thingy



const Tab = (tabList) => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <nav id="tab">
      {
        tabList.map((tabConfig, index) => (
          <a href={tabConfig.href} data-active={activeTab === index} />
        )
      }
    </nav>
  );
};


Enter fullscreen mode Exit fullscreen mode

Not much change huh? Okay, let us take this slow from here.

What I did just now, is that I set data-active to the <a> instead of className. So, I don't have to declare unnecessary className conditions.

Before we are going to do that, we need to change something to the CSS we had previously.



#tab a {
  // Initial state of tab
}

#tab a[data-active] {
  // Active state of tab
}


Enter fullscreen mode Exit fullscreen mode

So, I removed the tab-active class, and replaced it to a[data-active] instead.

What are we improving right here? It is readability of the implementation. Instead of playing the guessing game of which class are we going to trigger, we just set the condition to the data attributes. With this, developer will understand which element/class/id is having states, as we treat pseudo selectors, like :hover!



.myComponent {
  // Initial state of component
}

.myComponent:hover {
  // Hover state of component
}

.myComponent[data-active] {
  // Active state of component
}


Enter fullscreen mode Exit fullscreen mode

Can I use value other than boolean? Well, of course, you can set other values to the data attributes, with a condition that it should be string.



.myComponent {
  // Initial setting of component
}

.myComponent[data-variant="primary"] {
  // Primary state of component
}


Enter fullscreen mode Exit fullscreen mode

This sounds interesting, right? Not convincing enough? Let us try to introduce a CSS framework - Tailwind CSS πŸ˜„


Here Comes The Trouble

Let us bring the original problem. Now with Tailwind!



const Tab = (tabList) => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <nav id="tab">
      {
        tabList.map((tabConfig, index) => (
          <a href={tabConfig.href} className={activeTab === index ? "text-blue-500" : ""} />
        )
      }
    </nav>
  );
};


Enter fullscreen mode Exit fullscreen mode

Well, this is not wrong, except that this might work on your local machine, but not in production after build. You might find the glitch, only to learn that Tailwind fails to tree shake the class for production build. The classes that we add in the condition only works if the behaviour is expected, but that is not the case. So, what are my options?

Of course, you can just provide condition for the whole component, right? πŸ€”



const Tab = (tabList) => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <nav id="tab">
      {
        tabList.map((tabConfig, index) => activeTab === index
          ? (
            <a href={tabConfig.href} className="text-blue-500" />
          ) : (
            <a href={tabConfig.href} />
          )
      }
    </nav>
  );
};


Enter fullscreen mode Exit fullscreen mode

One, we are duplicating codes, and two, we are reducing the readability with all those ternaries. Just imagine that the next developer have to come in and guess what are you trying to do here. So, I am bringing the better suggestion! Let's use data attributes!



const Tab = (tabList) => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <nav id="tab">
      {
        tabList.map((tabConfig, index) => (
          <a href={tabConfig.href} className="data[active]:text-blue-500" data-active={activeTab === index} />
        )
      }
    </nav>
  );
};


Enter fullscreen mode Exit fullscreen mode

Now, let's us break this into parts:

  • I am adding data-active to the a element to control the state.
  • I am adding data[active]: to the className to trigger the style if the data-active returns true.
  • As the className is no longer in conditions, the behaviour can be expected, thus this approach is safe from tree shaking. Thus, it works better!

Okay, let's spice things up! I will add all needed styles using Tailwind to see how it will affect the readability when using data attributes.



const Tab = (tabList) => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <nav id="tab">
      {
        tabList.map((tabConfig, index) => (
          <a href={tabConfig.href} className="px-2 py-4 text-black bg-slate-200 data[active]:text-blue-500 data[active]:bg-zinc-200" data-active={activeTab === index} />
        )
      }
    </nav>
  );
};


Enter fullscreen mode Exit fullscreen mode

As usual, the dilemma of using Tailwind is to increase the class names, right? To improve this, I will "kinda" introduce my own way of breaking classes into parts.



/**
 * @param classNames string[]
 **/
const c = (classNames) => classNames.join(" ");

const Tab = (tabList) => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <nav id="tab">
      {
        tabList.map((tabConfig, index) => (
          <a href={tabConfig.href}
            className={c([
              "px-2 py-4 text-black bg-slate-200",
              "data[active]:text-blue-500 data[active]:bg-zinc-200"
            ])}
            data-active={activeTab === index}
          />
        )
      }
    </nav>
  );
};


Enter fullscreen mode Exit fullscreen mode

What I did, is I just separate it into lines, then use the join function to reassemble the class again. Since it doesn't involve any conditions, the class are still safe 😁. With this, I can separate className to lines, which each represents each state. Readable, tree-shakable and working!

You can learn more about how data attributes can be used in Tailwind if you are interested!


Conclusion

Data attributes are beneficial for a lot of thing, and CSS string is one of them. We can leverage CSS data attributes for this purpose. Of course, this is just one of the way to do this, but data attributes are just too awesome to let it by. Now you know!

Well, I hope this strengthen your knowledge on CSS. Keep healthy folks, and peace be upon ya!

πŸ’– πŸ’ͺ πŸ™… 🚩
alserembani
Atif Aiman

Posted on June 9, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related