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.
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.