{"id":5224,"date":"2025-02-25T14:04:17","date_gmt":"2025-02-25T19:04:17","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=5224"},"modified":"2025-02-25T14:22:38","modified_gmt":"2025-02-25T19:22:38","slug":"how-to-use-attr-in-css-for-columns-colors-and-font-size","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/how-to-use-attr-in-css-for-columns-colors-and-font-size\/","title":{"rendered":"How to Use attr() in CSS for Columns, Colors, and Font-Size"},"content":{"rendered":"\n<p>I&#8217;ve personally put &#8220;advanced <code>attr()<\/code> usage&#8221; on my CSS wishlists for years and years. All the sudden we&#8217;re seeing support for it start to drop! Props to the Chrome gang and others for shipping and highlighting this wonderfulness. I&#8217;m avoiding being entirely comprehensive about this feature in this post, so it&#8217;s worth perusing other coverage:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Una Kravets: <a href=\"https:\/\/una.im\/advanced-attr\/#attr-irl-product-card-demo\">New capabilities for attr()<\/a><\/li>\n\n\n\n<li>Bramus Van Damme: <a href=\"https:\/\/developer.chrome.com\/blog\/advanced-attr\">CSS&nbsp;attr() gets an upgrade<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/attr\">MDN attr() docs<\/a> are updated.<\/li>\n\n\n\n<li>Amit Merchant: <a href=\"https:\/\/www.amitmerchant.com\/attr-function-types-css\/\">The attr() function in CSS now supports types<\/a><\/li>\n<\/ul>\n\n\n\n<p>I thought I would chime in with my own examples so help smash it into my brain and to create an easy reference for soon to be <em>classic<\/em> use cases. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Deal<\/h2>\n\n\n\n<p>The value of <code>attr()<\/code>used to be <em>always<\/em> a string, which made the value pretty much <em>only<\/em> useful for the <code>content<\/code> property and certain niche text effects.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">div<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> {\n  <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-built_in\">attr<\/span>(data-title); \n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Even if you put something like <code>data-number=\"10\"<\/code>, you could never get a proper number 10 to use in CSS. The same with like <code>data-size=\"3rem\"<\/code>, no chance of actually getting <code>3rem<\/code> to actually <em>use.<\/em> <\/p>\n\n\n\n<p>Now you can! You just have to <em>declare<\/em> what type it will be.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">div<\/span> {\n  <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-built_in\">attr<\/span>(data-font-size type(&lt;length&gt;));\n  <span class=\"hljs-attribute\">grid-column-start<\/span>: <span class=\"hljs-built_in\">attr<\/span>(data-column-start type(&lt;integer&gt;));\n\n  <span class=\"hljs-comment\">\/* example with a \"fallback\" *\/<\/span>\n  <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-built_in\">attr<\/span>(data-color type(&lt;color&gt;), black);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"learn-more\">This usage is Chrome-only as I write\/publish here, but that <a href=\"https:\/\/caniuse.com\/css3-attr\">will change over time<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Types<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;string&gt;<br>&lt;angle&gt;<br>&lt;color&gt;<br>&lt;custom-ident&gt;<br>&lt;integer&gt;<br>&lt;length&gt;<br>&lt;length-percentage&gt;<br>&lt;number&gt;<br>&lt;percentage&gt;<br>&lt;resolution&gt;<br>&lt;time&gt;<br>&lt;transform-function&gt;<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Using <code>attr()<\/code> for Grid Control<\/h2>\n\n\n\n<p>Here&#8217;s an example where:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>data-row<\/code> explicitly sets what row an element is on<\/li>\n\n\n\n<li><code>data-columns<\/code> explicitly sets how many columns an element should span<\/li>\n\n\n\n<li><code>data-column-start<\/code> and <code>data-column-end<\/code> set on where a column should start or end<\/li>\n<\/ul>\n\n\n\n<p>That&#8217;s just one possible implementation where you essentially define your own API for what you want to allow and how you want to apply it. <\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_LEYRzMz\" src=\"\/\/codepen.io\/anon\/embed\/LEYRzMz?height=500&amp;theme-id=47434&amp;slug-hash=LEYRzMz&amp;default-tab=css,result\" height=\"500\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed LEYRzMz\" title=\"CodePen Embed LEYRzMz\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Using <code>attr()<\/code> for Colors<\/h2>\n\n\n\n<p>Just naming a color you want to use is nice!<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_VYwKyGe\" src=\"\/\/codepen.io\/anon\/embed\/VYwKyGe?height=450&amp;theme-id=47434&amp;slug-hash=VYwKyGe&amp;default-tab=css,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed VYwKyGe\" title=\"CodePen Embed VYwKyGe\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>This example highlights a nicety of having the additional &#8220;layer&#8221; of CSS to handle things, as opposed to very direct inline styles. Here I&#8217;m not just setting <em>one<\/em> color, but I&#8217;m using the color given for the actual <code>color<\/code>, then taking that same color and <code>color-mix()<\/code>ing in some black to use as the background color. <\/p>\n\n\n\n<p>To highlight the control you have even more, we could use <code>min()<\/code> and <code>max()<\/code> values within the relative color syntax to ensure, for example, the color has a minimum level of brightness that we might think is best for readability. <\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_MYWjGzz\" src=\"\/\/codepen.io\/anon\/embed\/MYWjGzz?height=450&amp;theme-id=47434&amp;slug-hash=MYWjGzz&amp;default-tab=css,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed MYWjGzz\" title=\"CodePen Embed MYWjGzz\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Above the dot before each line is the actual color being set as an attribute, but then when we use it to color a word in the line, we&#8217;re converting the color to <code>oklch()<\/code> and ensure the &#8220;l&#8221; part (lightness) has a minimum value of <code>0.9<\/code> (with <code>max(l, 0.9)<\/code>) to ensure that. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Using <code>attr()<\/code> for Font Sizes<\/h2>\n\n\n\n<p>Setting a <code>data-font-size<\/code> is easy peasy. But here I&#8217;ll jump forward to the &#8220;extra control&#8221; part right away. Perhaps your design system has strong rules about font sizing and you only allow increments of 5px. Using CSS <code>round()<\/code> we could make that happen with this approach.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_zxYKaxO\" src=\"\/\/codepen.io\/anon\/embed\/zxYKaxO?height=600&amp;theme-id=47434&amp;slug-hash=zxYKaxO&amp;default-tab=result\" height=\"600\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed zxYKaxO\" title=\"CodePen Embed zxYKaxO\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Others?<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>How about <code>data-gap<\/code> as a utility to just change the gap, but be able to round it to particular values in a design system?<\/li>\n\n\n\n<li>How about <a href=\"https:\/\/www.bram.us\/2025\/01\/20\/css-attr-gets-an-upgrade\/\">automatic <code>view-transition-name<\/code>s<\/a> like Bramus did up. <\/li>\n\n\n\n<li>One-off borders with a <code>data-border<\/code> seems like nice control to offer. Or even individual borders. Or individual parts of individual borders like <code>data-border-bottom-width<\/code>. <\/li>\n\n\n\n<li>It occurs to me that setting the value of a custom property to the attribute value is a way of passing the value lower in the DOM tree, which doesn&#8217;t seem possible otherwise, like&#8230;<\/li>\n<\/ul>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">&#91;data-button-color] {\n  --button-color: attr(data-button-color type(<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">color<\/span>&gt;<\/span>));\n\n  button {\n    color: var(--button-color);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>With that it seems like you could do stuff like <code>data-size-of-close-button<\/code> on a <code>&lt;dialog&gt;<\/code> and then access that informaton wherever you implement the close button within there.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why always use <code>data-*<\/code>?<\/h2>\n\n\n\n<p>You don&#8217;t have to. I just like the idea of not polluting attribute names. If the web platform one day really really wanted to support a <code>gap<\/code> attribute for whatever reason, but found through analyzing websites that too many websites rawdogged it already because of this feature, that would be a bummer to me. The <code>data-*<\/code> namespace was created just for this reason, so we might as well use it. Plus you get <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/HTMLElement\/dataset\">the JavaScript <code>dataset<\/code> property<\/a> to use for free if you do. <\/p>\n\n\n\n<p>ok bye and please shower me with more ideas for this. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>You can pluck off values from HTML attributes that actually have types now, so if you put data-font-size=&#8221;2.2rem&#8221; on an element you could actually, ya know, honor that.<\/p>\n","protected":false},"author":1,"featured_media":5241,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[300,7],"class_list":["post-5224","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-attr","tag-css"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/02\/column-control.png?fit=1496%2C770&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/5224","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=5224"}],"version-history":[{"count":11,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/5224\/revisions"}],"predecessor-version":[{"id":5242,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/5224\/revisions\/5242"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/5241"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=5224"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=5224"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=5224"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}