{"id":261888,"date":"2017-11-02T08:21:03","date_gmt":"2017-11-02T15:21:03","guid":{"rendered":"http:\/\/css-tricks.com\/?p=261888"},"modified":"2019-05-02T16:35:48","modified_gmt":"2019-05-02T23:35:48","slug":"css-attr-function-got-nothin-custom-properties","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/css-attr-function-got-nothin-custom-properties\/","title":{"rendered":"The CSS attr() function got nothin’ on custom properties"},"content":{"rendered":"

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<\/em> a way CSS can get its hands on data in HTML, so long as that data is within an attribute on that HTML element.<\/p>\n

<\/p>\n

It’s like this:<\/p>\n

<\/div>\n
div::after {\r\n  content: attr(data-whatever);\r\n}<\/code><\/pre>\n

That’s certainly interesting. You could use it for (rather inaccessible) tooltips, for example:<\/p>\n

<button data-tooltip=\"Information only mouse-having sighted people will see.\">\r\n  Button\r\n<\/button><\/code><\/pre>\n
button:hover::after {\r\n  content: attr(data-tooltip);\r\n  \/* positioned and styled and whatnot *\/\r\n  \/* ya, a :focus style would buy you a tad more a11y *\/\r\n}<\/code><\/pre>\n

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.<\/p>\n

Here’s a better use case. There is an old print stylesheet chestnut where you use attr()<\/code> to add the URL’s to links, so you can actually see what a link is linking to:<\/p>\n

@media (print) {\r\n  a[href]::after {\r\n    content: \" (\" attr(href) \" )\";\r\n  }\r\n}<\/code><\/pre>\n

That’s clever. But what else? Could you pass a color down?<\/p>\n

<h2 data-color=\"#f06d06\">\r\n  Custom Colored Header\r\n<\/h2><\/code><\/pre>\n

That’s not invalid, but it isn’t useful.<\/p>\n

h2 {\r\n  \/* Not gonna work *\/\r\n  color: attr(data-color);\r\n}<\/code><\/pre>\n

The value from attr()<\/code> is a string<\/strong>. Even though that string is in the same format as a hex code, it won’t be used as a hex code. <\/p>\n

Nor can you pass a URL that can actually be used in something like background-image()<\/code>. Nor you can pass a unit like 3<\/code>, 20px<\/code> or 4rem<\/code> or 0.8vw<\/code>. <\/p>\n

CSS’s attr() function is only<\/em> strings, and strings are only<\/em> really useful as content<\/code>, and content<\/code> (being unselectable and somewhat inaccessible) isn’t particularly useful anyway.<\/strong> You can’t select the text of psuedo content, for example, nor search for it, making it rather inacessible. <\/p>\n

You know what can<\/em> pass any sort of value and is equally easy to implement as attributes?<\/h3>\n

CSS custom properties!<\/p>\n

You can pop them right into the style<\/code> attribute of any element. Now those values are available to that element:<\/p>\n

<button \r\n  style=\"\r\n    --tooltip-string: 'Ug. Tooltips.';\r\n    --tooltip-color: #f06d06;\r\n    --tooltip-font-size: 11px;\r\n    --tooltip-top: -10px\r\n  \"\r\n>\r\n  Button\r\n<\/button><\/code><\/pre>\n

We’re passing a string to CSS above, but also<\/em> a color and length values. Those values are immediately usable as-is:<\/p>\n

button::after {\r\n  content: var(--tooltip-string);\r\n  color: var(--tooltip-color);\r\n  font-size: var(--tooltip-font-size);\r\n}<\/code><\/pre>\n

Here’s that demo with some fiddly “logic” (would need to be improved a lot to be actually useful) to allow variations:<\/p>\n

See the Pen CSS Custom Properies Mo’ Betta’ than attr()<\/a> by Chris Coyier (@chriscoyier<\/a>) on CodePen<\/a>.<\/p>\n

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<\/a>.<\/p>\n

What about some other “good” use cases for attr()?<\/h3>\n

One that comes up a lot is responsive data tables. Imagine a table with headers along a top row and rows of data below:<\/p>\n

<table>\r\n  <thead>\r\n  <tr>\r\n    <th>First Name<\/th>\r\n    <th>Last Name<\/th>\r\n    ....\r\n  <\/tr>\r\n  <\/thead>\r\n  <tbody>\r\n  <tr>\r\n    <td>Chris<\/td>\r\n    <td>Coyier<\/td>\r\n    ...\r\n  <\/tr>\r\n  ...\r\n  <\/tbody>\r\n<\/table><\/code><\/pre>\n

Rows of data like that might become problematic on small screens (too wide). So in a reponsive<\/em> data table, we might hide<\/em> that top row, and show labels on a per-cell basis instead. <\/p>\n

@media (max-width: 500px) {\r\n  thead {\r\n    display: none;\r\n  }\r\n  \/* Need to reveal another label now that we've hidden the normal labels *\/\r\n}<\/code><\/pre>\n

Where does that label come from? We could do…<\/p>\n

 . ...\r\n  <tr>\r\n    <td data-label=\"First Name\">Chris<\/td>\r\n    <td data-label=\"Last Name\">Coyier<\/td>\r\n    ...\r\n  <\/tr><\/code><\/pre>\n

Then:<\/p>\n

td::before { \r\n  content: attr(data-label);\r\n  \/* Also display: block things and such *\/ \r\n}<\/code><\/pre>\n

That’s a pretty good use case. If we use some kinda of accessible hiding method for that <thead><\/code>, it might even pass a11y muster.<\/p>\n

But this same exact thing is doable with CSS custom properties…<\/p>\n

 . ...\r\n  <tr>\r\n    <td style=\"--label: 'First Name';\">Chris<\/td>\r\n    <td style=\"--label: 'Last Name';\">Chris<\/td>\r\n    ...\r\n  <\/tr><\/code><\/pre>\n
td::before { \r\n  content: var(--label);\r\n  ...\r\n}<\/code><\/pre>\n
\n

Eric Bidelman pointed me to a method<\/a> of using psueudo content to show an input’s value.<\/p>\n

<style>\r\n  input {\r\n    vertical-align: middle;\r\n    margin: 2em;\r\n    font-size: 14px;\r\n    height: 20px;\r\n  }\r\n  input::after {\r\n    content: attr(data-value) '\/' attr(max);\r\n    position: relative;\r\n    left: 135px;\r\n    top: -20px;\r\n  }\r\n<\/style>\r\n\r\n<input type=\"range\" min=\"0\" max=\"100\" value=\"25\">\r\n\r\n<script>\r\n  var input = document.querySelector('input');\r\n\r\n  input.dataset.value = input.value; \/\/ Set an initial value.\r\n\r\n  input.addEventListener('change', function(e) {\r\n    this.dataset.value = this.value;\r\n  });\r\n<\/script><\/code><\/pre>\n

That feels a smidge dangerous to me since I didn’t think pseudo content was supposed to work on replaced elements like an <input><\/code>. It’s probably a job for output<\/a>, 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.<\/p>\n


\n

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=\"\"<\/code> and ::before { content: attr(data-line-number); }<\/code>.<\/p>\n

\"\"<\/figure>\n

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.<\/p>\n

<td style=\"--line-num: 5\"> ... <\/td><\/code><\/pre>\n

You could argue this is better because if you did<\/em> want to use CSS counters, you could use that first value to kick things off and not need it on every line.<\/p>\n

See the Pen Line Numbering<\/a> by Chris Coyier (@chriscoyier<\/a>) on CodePen<\/a>.<\/p>\n

Same deal with typographic trickery involving duplicating text in CSS for stylistic reasons. Check out this cool demo by Mandy Michael<\/a> using attr()<\/code>. I’m sure you can imagine how --heading: \"Fracture\";<\/code> could do the trick there. <\/p>\n

The CSS3 Values spec (in Candidate Recommendation) has a way to make attr() useful<\/h3>\n

I’m not sure it matters much, as I’d argue CSS custom properties are a near total replacement for attr()<\/code>, but the spec does specifically cover this<\/a>, presumably as an attempt to make it more useful. <\/p>\n

The idea is to set the type of value as you grab it in CSS. <\/p>\n

<div data-color=\"red\">Some Words<\/div><\/code><\/pre>\n
div {\r\n  color: attr(data-color color);\r\n}<\/code><\/pre>\n

Or…<\/p>\n

<span data-size=\"50\">span<\/span><\/code><\/pre>\n
span {\r\n  font-size: attr(data-size px);\r\n}<\/code><\/pre>\n

But as far as I can tell, no browser supports this.<\/p>\n","protected":false},"excerpt":{"rendered":"

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 […]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4],"tags":[1351,1036],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":356977,"url":"https:\/\/css-tricks.com\/control-layout-in-a-multi-directional-website\/","url_meta":{"origin":261888,"position":0},"title":"Control Layout in a Multi-Directional Website","date":"November 23, 2021","format":false,"excerpt":"Many business websites need a multilingual setup. As with anything development-related, implementing one in an easy, efficient, and maintainable way is desirable. Designing and developing to be ready for multiple languages, whether it happens right at launch or is expected to happen at any point in the future, is smart.\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/11\/s_EB2B5807BB67AB4D3A5A08E24A95E2DA3A380C9ECA54FED1EDE0A35A4D308E5C_1635162442827_design.jpg?fit=1200%2C1103&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":303923,"url":"https:\/\/css-tricks.com\/a-complete-guide-to-calc-in-css\/","url_meta":{"origin":261888,"position":1},"title":"A Complete Guide to calc() in CSS","date":"March 17, 2020","format":false,"excerpt":"CSS has a special calc() function for doing basic math. In this guide, let's cover just about everything there is to know about this very useful function. Here's an example: .main-content { \/* Subtract 80px from 100vh *\/ height: calc(100vh - 80px); } In this guide, let's cover just about\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/03\/calc-guide.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":304025,"url":"https:\/\/css-tricks.com\/4-ways-to-animate-the-color-of-a-text-link-on-hover\/","url_meta":{"origin":261888,"position":2},"title":"4 Ways to Animate the Color of a Text Link on Hover","date":"March 3, 2020","format":false,"excerpt":"Let\u2019s create a pure CSS effect that changes the color of a text link on hover\u2026 but slide that new color in instead of simply swapping colors. There are four different techniques we can use to do this. Let\u2019s look at those while being mindful of important things, like accessibility,\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":317131,"url":"https:\/\/css-tricks.com\/reactive-jquery-for-spaghetti-fied-legacy-codebases-or-when-you-cant-have-nice-things\/","url_meta":{"origin":261888,"position":3},"title":"Reactive jQuery for Spaghetti-fied Legacy Codebases (or When You Can\u2019t Have Nice Things)","date":"July 22, 2020","format":false,"excerpt":"I can hear you crying out now: \u201cWhy on Earth would you want to use jQuery when there are much better tools available? Madness! What sort of maniac are you?\u201d These are reasonable questions, and I\u2019ll answer them with a little bit of context. In my current job, I am\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/07\/spaghetti.jpg?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":307611,"url":"https:\/\/css-tricks.com\/property\/","url_meta":{"origin":261888,"position":4},"title":"@property","date":"April 25, 2020","format":false,"excerpt":"The @property is totally new to me, but I see it's headed to Chrome, so I suppose it's good to know about! There is a draft spec and an \"intent to ship\" document. The code from that document shows: @property --my-property { syntax: \"\"; initial-value: green; inherits: false; } We've\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":302917,"url":"https:\/\/css-tricks.com\/a-complete-guide-to-data-attributes\/","url_meta":{"origin":261888,"position":5},"title":"A Complete Guide to Data Attributes","date":"February 17, 2020","format":false,"excerpt":"Everything you ever wanted to know about data attributes in HTML, CSS, and JavaScript.","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/02\/data-attribute-guide.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/261888"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=261888"}],"version-history":[{"count":12,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/261888\/revisions"}],"predecessor-version":[{"id":287301,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/261888\/revisions\/287301"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=261888"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=261888"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=261888"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}