Tiffany B. Brown

17 November 2014

Using attr(), with pseudo-elements and JavaScript

One of the lesser-used features of CSS is the attr() function. Like calc(), it's used to set the value of a CSS property. Though first defined as a value for the content property in CSS2, it's since been re-defined as a value for just about any CSS property.

I'm getting ahead of the state of support, though. Right now, we can only use attr() with content. What does it do? attr() accepts an HTML attribute as its argument, and returns the value of that attribute as the value for that property. This doesn't seem like such a big deal, I'm sure. But it's actually pretty useful for setting the content of pseudo-elements to create text effects. Take the following HTML.

<p data-txt="This is amazing!">This is amazing!</p>

We've set a data-txt attribute is an exact copy of the text contained between the <p> and </p> tags. Now in our CSS, we can use the attr() function.

p {
    background: #f3c;
    width: 50%;
    margin: auto;
}
p::before, p::after {
    content: attr(data-txt);
    display: block;
}
p::before{
    background: #9fc;
}
p::after{
    background: #9cf;
}

The result should resemble what follows below.

Figure 1: What it looks like to use `attr()` in combination with pseudo-elements.

Adrian Roselli used this to brilliant effect in his 2012 demo and blog post Chromatic Type with Pseudo Elements.

This is all well and good, but perhaps you like to keep your HTML a little bit cleaner, or you don't want to remember to add a data-* attribute. Well, we can take this one step further with some DOM scripting. The approach is simple: we'll set a data-txt attribute using DOM scripting.

window.addEventListener('DOMContentLoaded', function(){
    var p = document.querySelector('p');
    p.dataset.txt = p.innerHTML;
},false); // The third parameter is optional in modern browsers

Here we've just copied the innerHTML of our paragraph element to the data-txt attribute when the DOMContentLoaded event fires. This works in Safari, Chrome, and Firefox. But Internet Explorer requires a little bit more.

To make this work in Internet Explorer, we need to change our selectors to class selectors instead of element selectors.

.triptych {
    background: #f3c;
    width: 50%;
    margin: auto;
}
.triptych::before, .triptych::after {
    content: attr(data-txt);
    display: block;
}
.triptych::before{
    background: #9fc;
}
.triptych::after{
    background: #9cf;
}

Then we need to add another line to our DOMContentLoaded handler.

window.addEventListener('DOMContentLoaded', function(){
    var p = document.querySelector('p');
    p.dataset.txt = p.innerHTML;
    p.classList.add('triptych');
},false);

This delays the effect until after the data-txt attribute has been set. Once our data-txt attribute is available on the p element, we can add the triptych class and resulting effect.

This works for multiple elements too. In the DOMContentLoaded handler, you would just use document.querySelectorAll and either Array.prototype.map or a for loop to add a class name to each element.