{"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 That’s certainly interesting. You could use it for (rather inaccessible) tooltips, for example:<\/p>\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 That’s clever. But what else? Could you pass a color down?<\/p>\n That’s not invalid, but it isn’t useful.<\/p>\n The value from Nor can you pass a URL that can actually be used in something like CSS’s attr() function is only<\/em> strings, and strings are only<\/em> really useful as CSS custom properties!<\/p>\n You can pop them right into the 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 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 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 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 Where does that label come from? We could do…<\/p>\n Then:<\/p>\n That’s a pretty good use case. If we use some kinda of accessible hiding method for that But this same exact thing is doable with CSS custom properties…<\/p>\n Eric Bidelman pointed me to a method<\/a> of using psueudo content to show an input’s value.<\/p>\n That feels a smidge dangerous to me since I didn’t think pseudo content was supposed to work on replaced elements like an Exploiting the fact that psuedo content can’t be copied is also clever. For example, GitHub does code block line numbering with div::after {\r\n content: attr(data-whatever);\r\n}<\/code><\/pre>\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
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
<h2 data-color=\"#f06d06\">\r\n Custom Colored Header\r\n<\/h2><\/code><\/pre>\n
h2 {\r\n \/* Not gonna work *\/\r\n color: attr(data-color);\r\n}<\/code><\/pre>\n
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
background-image()<\/code>. Nor you can pass a unit like
3<\/code>,
20px<\/code> or
4rem<\/code> or
0.8vw<\/code>. <\/p>\n
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
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
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
What about some other “good” use cases for attr()?<\/h3>\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
@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
. ...\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
td::before { \r\n content: attr(data-label);\r\n \/* Also display: block things and such *\/ \r\n}<\/code><\/pre>\n
<thead><\/code>, it might even pass a11y muster.<\/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<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
<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
\ndata-line-number=\"\"<\/code> and
::before { content: attr(data-line-number); }<\/code>.<\/p>\n