Normally, the connection between CSS and HTML is that CSS selectors match HTML elements, and the CSS styles them. CSS doesn’t know about the actual content in the HTML. But there is a way CSS can get its hands on data in HTML, so long as that data is within an attribute on that HTML element.
It’s like this:
div::after {
content: attr(data-whatever);
}
That’s certainly interesting. You could use it for (rather inaccessible) tooltips, for example:
<button data-tooltip="Information only mouse-having sighted people will see.">
Button
</button>
button:hover::after {
content: attr(data-tooltip);
/* positioned and styled and whatnot */
/* ya, a :focus style would buy you a tad more a11y */
}
But you can’t put HTML in the attribute value, so those tooltips are limited to a string value, and couldn’t have a title, link, or anything like that inside them.
Here’s a better use case. There is an old print stylesheet chestnut where you use attr()
to add the URL’s to links, so you can actually see what a link is linking to:
@media (print) {
a[href]::after {
content: " (" attr(href) " )";
}
}
That’s clever. But what else? Could you pass a color down?
<h2 data-color="#f06d06">
Custom Colored Header
</h2>
That’s not invalid, but it isn’t useful.
h2 {
/* Not gonna work */
color: attr(data-color);
}
The value from attr()
is a string. Even though that string is in the same format as a hex code, it won’t be used as a hex code.
Nor can you pass a URL that can actually be used in something like background-image()
. Nor you can pass a unit like 3
, 20px
or 4rem
or 0.8vw
.
CSS’s attr() function is only strings, and strings are only really useful as content
, and content
(being unselectable and somewhat inaccessible) isn’t particularly useful anyway. You can’t select the text of psuedo content, for example, nor search for it, making it rather inacessible.
You know what can pass any sort of value and is equally easy to implement as attributes?
CSS custom properties!
You can pop them right into the style
attribute of any element. Now those values are available to that element:
<button
style="
--tooltip-string: 'Ug. Tooltips.';
--tooltip-color: #f06d06;
--tooltip-font-size: 11px;
--tooltip-top: -10px
"
>
Button
</button>
We’re passing a string to CSS above, but also a color and length values. Those values are immediately usable as-is:
button::after {
content: var(--tooltip-string);
color: var(--tooltip-color);
font-size: var(--tooltip-font-size);
}
Here’s that demo with some fiddly “logic” (would need to be improved a lot to be actually useful) to allow variations:
See the Pen CSS Custom Properies Mo’ Betta’ than attr() by Chris Coyier (@chriscoyier) on CodePen.
This really isn’t any more accessible, for the record. If I were implementing tooltips for real, I’d probably read the heck out of this.
What about some other “good” use cases for attr()?
One that comes up a lot is responsive data tables. Imagine a table with headers along a top row and rows of data below:
<table>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
....
</tr>
</thead>
<tbody>
<tr>
<td>Chris</td>
<td>Coyier</td>
...
</tr>
...
</tbody>
</table>
Rows of data like that might become problematic on small screens (too wide). So in a reponsive data table, we might hide that top row, and show labels on a per-cell basis instead.
@media (max-width: 500px) {
thead {
display: none;
}
/* Need to reveal another label now that we've hidden the normal labels */
}
Where does that label come from? We could do…
. ...
<tr>
<td data-label="First Name">Chris</td>
<td data-label="Last Name">Coyier</td>
...
</tr>
Then:
td::before {
content: attr(data-label);
/* Also display: block things and such */
}
That’s a pretty good use case. If we use some kinda of accessible hiding method for that <thead>
, it might even pass a11y muster.
But this same exact thing is doable with CSS custom properties…
. ...
<tr>
<td style="--label: 'First Name';">Chris</td>
<td style="--label: 'Last Name';">Chris</td>
...
</tr>
td::before {
content: var(--label);
...
}
Eric Bidelman pointed me to a method of using psueudo content to show an input’s value.
<style>
input {
vertical-align: middle;
margin: 2em;
font-size: 14px;
height: 20px;
}
input::after {
content: attr(data-value) '/' attr(max);
position: relative;
left: 135px;
top: -20px;
}
</style>
<input type="range" min="0" max="100" value="25">
<script>
var input = document.querySelector('input');
input.dataset.value = input.value; // Set an initial value.
input.addEventListener('change', function(e) {
this.dataset.value = this.value;
});
</script>
That feels a smidge dangerous to me since I didn’t think pseudo content was supposed to work on replaced elements like an <input>
. It’s probably a job for output, and the JavaScript would be essentially the same. You could use pseudo content with the additional element, but there’s really no need for that.
Exploiting the fact that psuedo content can’t be copied is also clever. For example, GitHub does code block line numbering with data-line-number=""
and ::before { content: attr(data-line-number); }
.
Nobody likes selecting line numbers when they are trying to copy code! Good use here (probably even more flexible than CSS counters), but again, something that CSS custom properties could handle as well.
<td style="--line-num: 5"> ... </td>
You could argue this is better because if you did want to use CSS counters, you could use that first value to kick things off and not need it on every line.
See the Pen Line Numbering by Chris Coyier (@chriscoyier) on CodePen.
Same deal with typographic trickery involving duplicating text in CSS for stylistic reasons. Check out this cool demo by Mandy Michael using attr()
. I’m sure you can imagine how --heading: "Fracture";
could do the trick there.
The CSS3 Values spec (in Candidate Recommendation) has a way to make attr() useful
I’m not sure it matters much, as I’d argue CSS custom properties are a near total replacement for attr()
, but the spec does specifically cover this, presumably as an attempt to make it more useful.
The idea is to set the type of value as you grab it in CSS.
<div data-color="red">Some Words</div>
div {
color: attr(data-color color);
}
Or…
<span data-size="50">span</span>
span {
font-size: attr(data-size px);
}
But as far as I can tell, no browser supports this.
This is not a css-trick. This has entered the realm of css-black-magic.
+1 CSS WIZARD
I just used the attr() function yesterday in combination with an abbr tag. Took a bit of finagling but at mobile sizes it shows the abbreviation and at tablet and desktop sizes it shows the full word. Was used to display a date with weekdays and months.
Totally see where you’re going, but at what point does it make more sense to just circle back around to inline styles? I guess it makes more sense to use custom props if you need to re-use the value in multiple places, but in the listed examples you could easily just write the styles inline.
I agree, this does circle back to using inline styles, and inline styles would be easier. I think using custom props just adds the cool factor.
True — but it does help a lot when you want to supply properties to pseudo elements. Solved a long standing design problem for me – a transparent dog-ear https://codepen.io/scootman/pen/XzpbGK. Possible without custom properties – but the HTML is very verbose
Regarding the tooltip example, that feels way hackier to put content in an inline style to do that custom property workaround. Wouldn’t it be easier and more accessible to do something like this?
Don’t get me wrong — the inline styles with custom properties are a neat trick for non-string stuff, but the second version of the example you gave is just as inaccessible as the first.
Yeah custom properties certainly don’t buy any a11y just by virtue of using them. aria-label seems like a pretty great attribute to use for them! Certainly better than
title
, of which there is no way to control the style of.I think aria-label is the best thing here. Thanks for the tip.
Look, attr can be used within jquery. For example, if you wanna to change the background of icon, you can play with attr. Ill show you:
$(“#button”).on(“mouseenter”, function(e) {
$(this).animate….attr(“diffuse-color”, “red”); });
This animates the background. Use the test case
I think you might be trying to say: JavaScript can access the value of an attribute and use the value in a way you intend, for example, a string used as a color value.
Yes, JavaScript can do that.
What abous CSP?
Content Security Policy?
Want to elaborate there?
The CSS3 Values spec is better because it allows us to set/remove attributes without having to splice strings out of the single style attribute.
a11y?!
W1y?
;)
Btw love the top tho. And agree with Goose that it is black magic! My favourite kind of tip. :)
Nice examples, i particularly liked the @media (print) example for links, had not seen or thought of that before, tres cool! :D
I learned 3 things from this article.
attr()
will always just provide a string, which is why it doesn’t work for colors etc. I understand now lolThe existence of
counter-reset
andcounter-increment
properties. Awesome!You can override custom props in
style
attr. Again, awesome!Thanks Chris.
One use-case you’re missing – which would only be covered by the new spec – is setting CSS vars more elegantly.
E.g. on a grid (I’m sure there are better use-cases, e.g. passing a dynamic color via an attribute):
See example at: https://jsfiddle.net/nilssolanki/z7r46n8k/