CSS 🤝 HTML Data Attributes
Tags: css, webdev, react
Published: 2023-06-09 18:02:35

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gncylks0346mdhmedgpx.jpeg)

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!

```html
<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>
```

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!

```css
#tab a {
  // Initial state of tab
}

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

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.

```javascript
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>
  );
};
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0vvtj6pxg3thjlc4ilyu.jpeg)

```react
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>
  );
};
```

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.

```css
#tab a {
  // Initial state of tab
}

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

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`!

```css
.myComponent {
  // Initial state of component
}

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

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

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.

```css
.myComponent {
  // Initial setting of component
}

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

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!

```react
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>
  );
};
```

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? 🤔

```react
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>
  );
};
```

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!

```react
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>
  );
};
```

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.

```react
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>
  );
};
```

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.

```react
/**
 * @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>
  );
};
```

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](https://tailwindcss.com/docs/hover-focus-and-other-states#data-attributes) 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!