tutorial // Jul 22, 2022

How to Use HTML Data Attributes with JavaScript and CSS

How to pass and interact with miscellaneous data passed to HTML elements via data attributes.

How to Use HTML Data Attributes with JavaScript and CSS

For this tutorial, we're going to use CheatCode's full-stack JavaScript framework, Joystick. Joystick brings together a front-end UI framework with a Node.js back-end for building apps.

To begin, we'll want to install Joystick via NPM. Make sure you're using Node.js 16+ before installing to ensure compatibility (give this tutorial a read first if you need to learn how to install Node.js or run multiple versions on your computer):

Terminal

npm i -g @joystick.js/cli

This will install Joystick globally on your computer. Once installed, next, let's create a fresh project:

Terminal

joystick create app

After a few seconds, you will see a message logged out to cd into your new project and run joystick start:

Terminal

cd app && joystick start

After this, your app should be running and we're ready to get started.

Adding data attributes to elements

To get started, we're going to modify the existing file at /ui/pages/index/index.js in our app to give us a blank slate to work with (this is already wired up to the root route at http://localhost:2600/ which will make it easy to test our work):

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  render: () => {
    return `
      <div>
      </div>
    `;
  },
});

export default Index;

Here, we're just replacing the existing code with a barebones Joystick component. This gives us an empty <div></div> tag in the browser and nothing else (if we load up http://localhost:2600 in the browser now, we won't see anything which is correct).

From here, let's update our HTML to include some extra tags with data attributes and explain what's happening:

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  render: () => {
    return `
      <div>
        <div class="text" data-story="He huffed, and he puffed, and blew the house down."></div>
        <div class="icon" magic="šŸ„³"></div>
      </div>
    `;
  },
});

export default Index;

Nearly identical, but inside of our empty <div></div> tag, we've added two additional divs. The first is given a class of text and the second is given a class of icon.

On the text div, we've added a data attribute data-story with a value set to a string telling a short story. Here, data-story is a non-standard HTML attribute. We can get away with this because we're using the data- prefix which tells HTML that this is a custom data attribute.

On the icon div, we've added an attribute magic which is given a value of šŸ„³, or, the "party face" emoji. Though our focus in this tutorial is on data attributes, this is worth pointing out. Technically speaking, you can add any custom attribute to an HTML tag (e.g., pizza="šŸ•") and the usage we see later will still work, however, the danger with this is that you might have conflicts with actual or supported HTML attributes. While most browsers will respect this, it can have surprising consequences (like getting picked up/omitted by an HTML linter).

We're doing this here as an example, but the recommendation is to prefix any custom attribute names with data-.

Retrieving and manipulating data attributes

At this point, the work we've done may seem fairly pointless beyond adding some extra metadata to our HTML. To make this more useful, let's take a look at how to retrieve and manipulate data attributes by adding some DOM event listeners to our HTML:

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  events: {
    'click [data-story]': (event) => {
      const story = event.target.getAttribute('data-story');
      event.target.setAttribute('data-story', story?.split('').reverse().join(''));
    },
    'click [magic]': () => {
      alert('It\'s an emoji, maaaan.');
    },
  },
  render: () => {
    return `
      <div>
        <div class="text" data-story="He huffed, and he puffed, and blew the house down."></div>
        <div class="icon" magic="šŸ„³"></div>
      </div>
    `;
  },
});

export default Index;

Here, we're making use of a Joystick component's ability to add DOM event listeners to elements rendered by our component. Here, we've added a click event listener for the [data-story] selector. This may look a bit odd. Typically, an event selector (or CSS selector, if you preferā€”the names are often used interchangeably) is a .class-like-this or an #id-like-this.

Here, we're selecting an element by its HTML attribute. To do it, we wrap the name of the attribute in square brackets []. Notice that we don't specify the value of the attribute, just the name of the attribute (the part before the = in our HTML).

The behavior here is identical to what you'd expect with a class or an ID; we're just using a different attribute to "target" or "find" the element in the DOM. Once we've got it, to our listener definition click [data-story], we pass a function that will be called when the click event is detected on elements with a data-story attribute.

Inside of that function, we take in the DOM event that was captured by the listener and first, retrieve the current value of our data attribute (storing the value in a variable story) by calling to the .getAttribute() method that's accessible on all DOM elements. Here, event.target represents the element the event was detected on. To .getAttribute(), we pass the name of the attribute we want to retrieve the value for.

To demonstrate the inverse of thisā€”setting an attribute on an element via JavaScriptā€”on the line below this, we again use the event.target value, but this time call to .setAttribute(), passing two arguments: the name of the attribute we want to set followed by the value we want to set it to.

For a little bit of fun, we take the story variable (the original value of our attribute) and use the .split() method to split the string into an array. Then, we call .reverse() on that resulting array followed by .join('') to join the array back into a string. In case it's not clear, we're just reversing the existing string value from our data attribute before setting it back onto our <div></div>.

To make sure this is all clear, recall that earlier we mentioned that we can add custom attributes without the data- prefix (though, caution should be used when doing this to avoid surprise issues). Like we hinted, while this should work in most browsers, don't rely on it. Regardless, using the same approach to our data- attribute, we add a click event listener on our [magic] attribute with click [magic]. All of the same rules apply, however, instead of retrieving the value here, we're just firing an alert() to demonstrate that we can add a listener to a custom attribute.

While that covers the primary use case for data attributes, next, we're going to look at a lesser-known feature of data attributes: the ability to use them in CSS.

Using data attributes in CSS

There are two ways to use data attributes (again, we use this as a "catch-all" term to refer to any HTML attribute to encourage good behavior) in CSS: as selectorsā€”similar to what we saw with event selectors aboveā€”and as values in our CSS. Let's add some CSS demonstrating these two techniques to our component and then explain how it's working:

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  css: `
    [data-story] {
      font-size: 18px;
      font-style: italic;
      color: red;
    }

    [data-story]:before {
      content: attr(data-story);
    }

    .icon:before {
      content: attr(magic);
    }

    [magic] {
      font-size: 80px;
    }
  `,
  events: { ... },
  render: () => {
    return `
      <div>
        <div class="text" data-story="He huffed, and he puffed, and blew the house down."></div>
        <div class="icon" magic="šŸ„³"></div>
      </div>
    `;
  },
});

export default Index;

Starting with our data-story <div></div>, in our CSS, we use the [data-story] selector we saw earlier to select the <div></div> and then apply some styles to it. Simple enough. Where things get interesting, however, is with CSS pseudo elements like :before and :after.

Here, using the same [data-story] selector, we append a :before property immediately after to say "we want to add styles for the :before pseudo element on elements with the data-story attribute." In CSS, a pseudo element is, like the name implies, an element that doesn't actually exist. Instead, pseudo elements only exist in memory and not the DOM (though in modern browsers, they show up in the "Elements" tab alongside your markup) but still show up on screen.

To define a pseudo-element, we need to specify a content property in our CSS rules (otherwise the element doesn't show up on screen). This is where things get interesting: in CSS, a special "function" exists called attr() which can be assigned to the content property in our CSS. To that function, we can pass the name of an HTML attribute we want to retrieve the value of, setting it as the content of our pseudo-element.

If we omit these styles, we'll notice that our <div></div> remains empty on screen. As soon as we add this, though, our pseudo :before element is populated with the content retrieved via attr(). Just like everything else we saw above, this also works for custom attributes not prefixed with data-.

If we pull up our app in the browser, we should see our stylized text and emoji on screen. Go ahead and click on them to see our events being applied.

Wrapping up

In this tutorial, we learned how to use HTML data attributes to add custom data to our HTML elements. We learned the difference between data- prefixed elements and elements without the data- prefix. Next, we learned how to add JavaScript DOM event listeners, using custom data attributes as our selector, learning how to modify an element's attributes on the fly. Finally, we learned how to use custom DOM attributes to style and dynamically set the content of a CSS pseudo-element via the attr() function.

Written By
Ryan Glover

Ryan Glover

CEO/CTO @ CheatCode