Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Fri, 14 Nov 2025 16:27:19 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 What You Need to Know about Modern CSS (2025 Edition) https://frontendmasters.com/blog/what-you-need-to-know-about-modern-css-2025-edition/ https://frontendmasters.com/blog/what-you-need-to-know-about-modern-css-2025-edition/#comments Fri, 19 Sep 2025 23:05:13 +0000 https://frontendmasters.com/blog/?p=6948 We published an edition of What You Need To Know about Modern CSS last year (2024), and for a while I really wasn’t sure if only a year later we’d have enough stuff to warrant and new yearly version. But time, and CSS, have rolled forward, and guess what? There is more this year than there was last. At least in this somewhat arbitrary list of “things Chris thinks are valuable to know that are either pretty fresh or have enjoyed a boost in browser support.”

Animate to Auto

What is this?

We don’t often set the height of elements that contain arbitrary content. We usually let elements like that be as tall as they need to be for the content. The trouble with that is we haven’t been able to animate from a fixed number (like zero) to whatever that intrinsic height is (or vice versa). In other words, animate to auto (or other sizing keywords like min-content and the like).

Now, we can opt-in to being able to animate to these keywords, like:

html {
  interpolate-size: allow-keywords;
  /* Now if we transition 
     "height: 0;" to "height: auto;" 
     anywhere, it will work */
}

If we don’t want to use an opt-in like that, alternatively, we can use the calc-size() function to make the transition work without needing interpolate-size.

.content {
  height: 3lh;
  overflow: hidden;
  transition: height 0.2s;
  
  &.expanded {
    height: calc-size(auto, size);
  }
}

Why should I care?

This is the first time we’ve ever been able to do this in CSS. It’s a relatively common need and it’s wonderful to be able to do it so naturally, without breaking behavior.

And it’s not just height (it could be any property that takes a size) and it’s not just auto (it could be any sizing keyword).

Support

Browser SupportJust Chrome.
Progressive EnhancementYes! Typically, this kind of animation isn’t a hard requirement, just a nice-to-have.
PolyfillNot really. The old fallbacks including things like animating max-height to a beyond-what-is-needed value, or using JavaScript to attempt to measure the size off-screen and then doing the real animation to that number. Both suck.

Usage Example

Popovers & Invokers

These are separate and independently useful things, and really rather HTML-focused, but it’s nice to show them off together as they complement each other nicely.

What is this?

A popover is an attribute you can put on any HTML element that essentially gives it open/close functionality. It will then have JavaScript APIs for opening and closing it. It’s similar-but-different to modals. Think of them more in the tooltip category, or something that you might want more than one of open sometimes.

Invokes are also HTML attributes that give us access to those JavaScript APIs in a declarative markup way.

Why should I care?

Implementing functionality at the HTML level is very powerful. It will work without JavaScript, be done in an accessible way, and likely get important UX features right that you might miss when implementing yourself.

Support

Browser SupportPopovers are everywhere, but invokers are Chrome only at time of publication.

There are sub-features here though, like popover="hint" which has slightly less support so far.
Progressive EnhancementNot so much. These type of functions typically need to work, so ensuring they do with a polyfill instead of handling multiple behaviors is best.
PolyfillYep! For both:

Popovers Polyfill
Invokers Polyfill

Usage Example

Remember there are JavaScript APIs for popovers also, like myPopover.showPopover() and secondPopover.hidePopover() but what I’m showing off here is specifically the HTML invoker controls for them. There are also some alternative HTML controls (e.g. popovertarget="mypopover" popovertargetaction="show") which I suppose are fine to use as well? But something feels better to me about the more generic command invokers approach.

Also — remember popovers pair particularly well with anchor positioning which is another CSS modern miracle.

@function

What is this?

CSS has lots of functions already. Think of calc(), attr(), clamp(), perhaps hundreds more. They are actually technically called CSS value functions as they always return a single value.

The magic with with @function is that now you can write your own.

@function --titleBuilder(--name) {
  result: var(--name) " is cool.";
}

Why should I care?

Abstracting logic into functions is a computer programming concept as old as computers itself. It can just feel right, not to mention be DRY, to put code and logic into a single shared place rather than repeat yourself or complicate the more declarative areas of your CSS with complex statements.

Support

Browser SupportChrome only
Progressive EnhancementIt depends on what you’re trying to use the value for. If it’s reasonable, it may be as simple as:

property: fallback;
property: --function();
PolyfillNot really. Sass has functions but are not based on the same spec and will not work the same.

Usage Example

Other Resources

if()

What is this?

Conceptually, CSS is already full of conditional logic. Selectors themselves will match and apply styles if they match an HTML element. Or media queries will apply if their conditions are met.

But the if() function, surprisingly, is the first specific logical construct that exists soley for the function of applying logical branches.

Why should I care?

Like all functions, including custom @functions like above, if() returns a single value. It just has a syntax that might help make for more readable code and potentially prevent certain types of code repetition.

Support

Browser SupportChrome only
Progressive EnhancementIt depends on the property/value you are using it with. If you’re OK with a fallback value, it might be fine to use.

property: fallback;
property: if(
style(--x: true): value;
else: fallback;
);
PolyfillNot really. CSS processes tend to have logical constructs like this, but they will not re-evaluate based on dynamic values and DOM placement and such.

Usage Example

Baking logic into a single value like this is pretty neat!

.grid {
  display: grid;
  grid-template-columns:
    if(
       media(width > 900px): repeat(auto-fit, minmax(200px, 1fr));
       media(width > 600px): repeat(3, 1fr);
       media(width > 300px): repeat(2, 1fr);
       else: 1fr;
    ); 
}

The syntax is a lot like a switch statement with as many conditions as you need. The first match wins.

if(
  condition: value;
  condition: value;
  else: value;
)

Conditions can be:

  • media()
  • supports()
  • style()

field-sizing

What is this?

The new field-sizing property in CSS is for creating form fields (or any editable element) that automatically grows to to the size of their contents.

Why should I care?

This is a need that developers have been creating in JavaScript since forever. The most classic example is the <textarea>, which makes a lot of sense to be sized to as large as the user entering information into it needs to be, without having to explicitly resize it (which is difficult at best on a small mobile screen). But inline resizing can be nice too.

Support

Browser SupportChrome and looks to be coming soon to Safari.
Progressive EnhancementYes! This isn’t a hard requirement usually but more of a UX nicety.
PolyfillThere is some very lightweight JavaScript to replicate this if you want to.

Usage Example

Custom Selects

What is this?

Styling the outside of a <select> has been decently possible for a while, but when you open it up, what the browser renders is an operating-system specific default. Now you can opt-in to entirely styleable select menus.

Why should I care?

Support

Browser SupportChrome only
Progressive Enhancement100%. It just falls back to a not-styled <select> which is fine.
PolyfillBack when this endeavor was using <selectlist> there was, but in my opinion the progressive enhancement story is so good you don’t need it.

Usage Example

First you opt-in then you go nuts.

select,
::picker(select) {
  appearance: base-select;
}

text-wrap

What is this?

The text-wrap property in CSS allows you to instruct the browser that it can and should wrap text a bit differently. For example, text-wrap: balance; will attempt to have each line of text as close to the same length as possible.

Why should I care?

This can be a much nicer default for large font-size elements like headers. It also can help with single-word-on-the-next-line orphans, but there is also text-wrap: pretty; which can do that, and is designed for smaller-longer text as well, creating better-reading text. Essentially: better typography for free.

Support

Browser Supportbalance is supported across the board but pretty is only Chrome and Safari so far.
Progressive EnhancementAbsolutely. As important as we might agree typography is, without these enhancements the text is still readable and accessible.
PolyfillThere is one for balance.

Usage Example

Resources

linear() easing

What is this?

I think this one a little confusing because linear as a keyword for transition-timing-function or animation-timing-function kinda means “flat and boring” (which is sometimes what you want, like when changing opacity for istance). But this linear() function actually means you’re about to do an easing approach that is probably extra fancy, like having a “bouncing” effect.

Why should I care?

Even the fancy cubic-bezier() function can only do a really limited bouncing affect with an animation timing, but the sky is the limit with linear() because it takes an unlimited number of points.

Support

Browser SupportAcross the board
Progressive EnhancementSure! You could fall back to a named easing value or a cubic-bezier()
PolyfillNot that I know of, but if fancy easing is very important to you, JavaScript libraries like GSAP have this covered in a way that will work in all browsers.

Usage Example

.bounce {
  animation-timing-function: linear(
    0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 13.6%, 0.25, 0.391, 0.563, 0.765,
    1, 0.891 40.9%, 0.848, 0.813, 0.785, 0.766, 0.754, 0.75, 0.754, 0.766, 0.785,
    0.813, 0.848, 0.891 68.2%, 1 72.7%, 0.973, 0.953, 0.941, 0.938, 0.941, 0.953,
    0.973, 1, 0.988, 0.984, 0.988, 1
  );
}

Resources

shape()

What is this?

While CSS has had a path() function for a while, it only took a 1-for-1 copy of the d attribute from SVG’s <path> element, which was forced to work only in pixels and has a somewhat obtuse syntax. The shape() function is basically that, but fixed up properly for CSS.

Why should I care?

The shape() function can essentially draw anything. You can apply it as a value to clip-path, cutting elements into any shape, and do so responsively and with all the power of CSS (meaning all the units, custom properties, media queries, etc). You can also apply it to offset-path() meaning placement and animation along any drawable path. And presumably soon shape-outside as well.

Support

Browser SupportIt’s in Chrome and Safari and flagged in Firefox, so everywhere fairly soon.
Progressive EnhancementProbably! Cutting stuff out and moving stuff along paths is usually the stuff of aesthetics and fun and falling back to less fancy options is acceptable.
PolyfillNot really. You’re better off working on a good fallback.

Usage Example

Literally any SVG path can be converted to shape().

.arrow {
  clip-path: shape(
    evenodd from 97.788201% 41.50201%, 
    line by -30.839077% -41.50201%, 
    curve by -10.419412% 0% with -2.841275% -3.823154% / -7.578137% -3.823154%, 
    smooth by 0% 14.020119% with -2.841275% 10.196965%, 
    line by 18.207445% 24.648236%, hline by -67.368705%, 
    curve by -7.368452% 9.914818% with -4.103596% 0% / -7.368452% 4.393114%, 
    smooth by 7.368452% 9.914818% with 3.264856% 9.914818%, 
    hline by 67.368705%, line by -18.211656% 24.50518%, 
    curve by 0% 14.020119% with -2.841275% 3.823154% / -2.841275% 10.196965%, 
    curve by 5.26318% 2.976712% with 1.472006% 1.980697% / 3.367593% 2.976712%, 
    smooth by 5.26318% -2.976712% with 3.791174% -0.990377%, line by 30.735919% -41.357537%, 
    curve by 2.21222% -7.082013% with 1.369269% -1.842456% / 2.21222% -4.393114%, 
    smooth by -2.21222% -7.082013% with -0.736024% -5.239556%, 
    close
  );
}

The natural re-sizeability and more readable syntax is big advantage over path():

More Powerful attr()

What is this?

The attr() function in CSS can pull the string value of the matching HTML element. So with <div data-name="Chris"> I can do div::before { content: attr(data-name); } to pull off an use “Chris” as a string. But now, you can apply types to the values you pull, making it a lot more useful.

Why should I care?

Things like numbers and colors are a lot more useful to pluck off and use from HTML attributes than strings are.

attr(data-count type(<number>))

Support

Browser SupportChrome only
Progressive EnhancementIt depends on what you’re doing with the values. If you’re passing through a color for a little aesthetic flourish, sure, it can be a enhancement that fallback to something else or nothing. If it’s crucial layout information, probably not.
PolyfillNot that I know of.

Usage Example

Reading Flow

What is this?

There are various ways to change the layout such that the visual order no longer matches the source order. The new reading-order property allow us to continue to do that while updating the behavior such that tabbing through the elements happens in a predictable manner.

Why should I care?

For a long time we’ve been told: don’t re-order layout! The source order should match the visual order as closely as possible, so that tabbing focus through a page happens in a sensible order. When you mess with the visual order and not source order, tabbing can become zig-zaggy and unpredictable, even causing scrolling, which is a bad experience and a hit to accessibility. Now we can inform the browser that we’ve made changes and to follow a tabbing order that makes sense for the layout style we’re using.

Support

Browser SupportChrome only
Progressive EnhancementNot particularly. We should probably not be re-ordering layout wildly until this feature is more safely across all browsers.
PolyfillNo, but if you were so-inclined you could (hopefully very intelligently) update the tabindex property of the elements to a sensible order.

Usage Example

.grid {
  reading-flow: grid-rows;
}

Re-ordering a grid layout is perhaps of the most common things to re-order, and having the tabbing order follow the rows after re-arranging is sensible, so that’s what the above line of code is doing. But you’ll need to set the value to match what you are doing. For instance if you are using flexbox layout, you’d likely set the value to flex-flow. See MDN for the list of values.

Resources

Stuff to Keep an Eye On

  • “Masonry” layout, despite having different preliminary implementations, is not yet finalized, but there is enough movement on it it feels like we’ll see that get sorted out next year. The most interesting development at the moment is the proposal of item-flow and how that could not only help with Masonry but bring other layout possibilities to other layout mechanisms beyond grid.
  • The CSS function random() is in Safari and it’s amazing.
  • The CSS property margin-trim is super useful and we’re waiting patiently to be able to use it more than just Safari.
  • The sibling-index() and sibling-count() functions are in Chrome and, for one thing, are really useful for staggered animations.
  • For View Transitions, view-transition-name: match-element; is awfully handy as it prevents us from needing to generate unique names on absolutely everything. Also — Firefox has View Transitions in development, so that’s huge.
  • We should be able to use calc() to multiply and divide with units (instead of requiring the 2nd to be unitless) soon, instead of needing a hack.
  • We never did get “CSS4” (Zoran explains nicely) but I for one still think some kind of named versioning system would be of benefit to everyone.
  • If you’re interested in a more straightforward list of “new CSS things” for say the last ~5 years, Adam Argyle has a great list.

Great Stuff to Remember

]]>
https://frontendmasters.com/blog/what-you-need-to-know-about-modern-css-2025-edition/feed/ 9 6948
Custom progress element using the attr() function https://frontendmasters.com/blog/custom-progress-element-using-the-attr-function/ https://frontendmasters.com/blog/custom-progress-element-using-the-attr-function/#respond Wed, 09 Apr 2025 18:39:02 +0000 https://frontendmasters.com/blog/?p=5537 is easier.]]> In a previous article, we combined two modern CSS features (anchor positioning and scroll-driven animations) to style the <progress> element without extra markup and create a cool component. Here’s that demo:

Anchor positioning was used to correctly place the tooltip shape while scroll-driven animations were used to get the progress value and show it inside the tooltip. Getting the value was the trickiest part of the experimentation. I invite you to read the previous article if you want to understand how scroll-driven animations helps us do it.

In this article, we will see an easier way to get our hands on the current value and explore another example of progress element.

At the time of writing, only Chrome (and Edge) have the full support of the features we will be using.

Article Series

Getting the progress value using attr()

This is the HTML element we are working with:

<progress value="4" max="10"></progress>

Nothing fancy: a progress element where you define the value and max attribute. Then we use the following CSS:

progress[value] {
  --val: attr(value type(<number>));
  --max: attr(max type(<number>),1);

  --x: calc(var(--val)/var(--max)); /* the percentage of progression */
}

We waited for this for too long! It’s finally here!

We can use attr() function not only with the content property but with any property including custom properties! The variable --x will contain the percentage of progression as a unit-less value in the range [0 1]. That’s all — no complex code needed.

We also have the ability to define the types (number, in our case) and specify fallback values. The max attribute is not mandatory so if not specified it will default to 1. Here is the previous demo using this new method instead of scroll-driven animations:

If we omit the tooltip and animation parts (explained in the previous article), the new code to get the value and use it to define the content of the tooltip and the color is a lot easier:

progress {
  --val: attr(value type(<number>));
  --max: attr(max type(<number>),1);

  --x: calc(100*var(--val)/var(--max));
  --_c: color-mix(in hsl,#E80E0D,#7AB317 calc(1%*var(--x)));
}
progress::value {
  background: var(--_c);
}
progress::before {
  content: counter(val) "%";
  counter-reset: val var(--x);
  background: var(--_c);
}

Should we forget about the “complex” scroll-driven animations method?

Nah — it can still be useful. Using attr() is the best method for this case and probably other cases but scroll-driven animations has one advantage that can be super handy: It can make the progress value available everywhere on the page.

I won’t get into the detail (as to not repeat the previous article) but it has to do with the scope of the timeline. Here is an example where I am showing the progress value within a random element on the page.

The animation is defined on the html element (the uppermost element) which means all the elements will have access to the --x variable.

If your goal is to get the progress value and style the element itself then using attr() should be enough but if you want to make the value available to other elements on the page then scroll-driven animations is the key.

Progress element with dynamic coloration

Now that we have our new way to get the value let’s create a progress element with dynamic coloration. This time, we will not fade between two colors like we did in the previous demo but the color will change based on the value.

A demo worth a thousand words:

As you can see, we have 3 different colors (red, orange and green) each one applied when the value is within a specific range. We have a kind of conditional logic that we can implement using various techniques.

Using multiple gradients

I will rely on the fact that a gradient with a size equal to 0 will be hidden so if we stack multiple gradients and control their visibility we can control which color is visible.

progress[value] {
  --val: attr(value type(<number>));
  --max: attr(max type(<number>),1);
  --_p: calc(100%*var(--val)/var(--max)); /* the percentage of progression */
}
progress[value]::-webkit-progress-value {
   background: 
    /* if (p < 30%) "red" */
    conic-gradient(red    0 0) 0/max(0%,30% - var(--_p)) 1%,
    /* else if (p < 60%) "orange" */
    conic-gradient(orange 0 0) 0/max(0%,60% - var(--_p)) 1%,
    /* else "green" */
    green;
}

We have two single-color gradients (red and orange) and a background-color (green). If, for example, the progression is equal to 20%, the first gradient will have a size equal to 10% 1% (visible) and the second gradient will have a size equal 40% 1% (visible). Both are visible but you will only see the top layer so the color is red. If the progression is equal to 70%, both gradients will have a size equal to 0% 1% (invisible) and the background-color will be visible: the color is green.

Clever, right? We can easily scale this technique to consider as many colors as you want by adding more gradients. Simply pay attention to the order. The smallest value is for the top layer and we increase it until we reach the bottom layer (the background-color).

Using an array of colors

A while back I wrote an article on how to create and manipulate an array of colors. The idea is to have a variable where you can store the different colors:

--colors: red, blue, green, purple;

Then be able to select the needed color using an index. Here is a demo taken from that article.

This technique is limited to background coloration but it’s enough for our case.

This time, we are not going to define precise values like we did with the previous method but we will only define the number of ranges.

  • If we define N=2, we will have two colors. The first one for the range [0% 50%[ and the second one for the range [50% 100%]
  • If we define N=3, we will have three colors. The first one for [0% 33%[, the second for [33% 66%[ and the last one for [66% 100%]

I think you get the idea and here is a demo with four colors:

The main trick here is to convert the progress value into an index and to do this we can rely on the round() function:

progress[value] {
  --n: 4; /* number of ranges */
  --c: #F04155,#F27435,#7AB317,#0D6759;
  
  --_v: attr(value type(<number>));
  --_m: attr(max type(<number>),1);
  --_i: round(down,100*var(--_v)/var(--_m),100/var(--n)); /* the index */
}

For N=4, we should have 4 indexes (0,1,2,3). The 100*var(--_v)/var(--_m) part is a value in the range [0 100] and 100/var(--n) part is equal to 25. Rounding a value to 25 means it should be a multiplier of 25 so the value will be equal to one of the following: 0, 25, 50, 75, 100. Then if we divide it by 25 we get the indexes.

But we have 5 indexes and not 4.

True, the value 100 alone will create an extra index but we can fix this by clamping the value to the range [0 99]

--_i: round(down,min(99,100*var(--_v)/var(--_m)),100/var(--n));

If the progress is equal to 100, we will use 99 because of the min() and the round will make it equal to 75. For the remaining part, check my other article to understand how I am using a gradient to select a specific color from the array we defined.

progress[value]::-webkit-progress-value {
   background:
     linear-gradient(var(--c)) no-repeat
     0 calc(var(--_i)*var(--n)*1%/(var(--n) - 1))/100% calc(1px*infinity);
}

Using an if() condition

What we have done until now is a conditional logic based on the progress value and CSS has recently introduced inline conditionals using an if() syntax.

The previous code can be written like below:

@property --_i {
  syntax: "<number>";
  inherits: true;
  initial-value: 0; 
}
progress[value] {
  --n: 4; /* number of ranges */
  
  --_v: attr(value type(<number>));
  --_m: attr(max type(<number>),1);
  --_i: calc(var(--n)*round(down,min(99,100*var(--_v)/var(--_m)),100/var(--n))/100); 
}
progress[value]::-webkit-progress-value {
   background: if(
     style(--_i: 0): #F04155;
     style(--_i: 1): #F27435;
     style(--_i: 2): #7AB317;
     style(--_i: 3): #0D6759;
    );
}

The code is self-explanatory and also more intuitive. It’s still too early to adopt this syntax but it’s a good time to know about it.

Using Style Queries

Similar to the if() syntax, we can also rely on style queries and do the following:

@property --_i {
  syntax: "<number>";
  inherits: true;
  initial-value: 0; 
}
progress[value] {
  --n: 4; /* number of ranges */
  
  --_v: attr(value type(<number>));
  --_m: attr(max type(<number>),1);
  --_i: calc(var(--n)*round(down,min(99,100*var(--_v)/var(--_m)),100/var(--n))/100); 
}
progress[value]::-webkit-progress-value {
  @container style(--_i: 0) {background-color: #F04155}
  @container style(--_i: 1) {background-color: #F27435}
  @container style(--_i: 2) {background-color: #7AB317}
  @container style(--_i: 3) {background-color: #0D6759}
}

We will also be able to have a range syntax and the code can be simplified to something like the below:

@property --_i {
  syntax: "<number>";
  inherits: true;
  initial-value: 0; 
}
progress[value] {
  --_v: attr(value type(<number>));
  --_m: attr(max type(<number>),1);
  --_i: calc(var(--_v)/var(--_m)); 
}
progress[value]::-webkit-progress-value {
  background-color: #0D6759;
  @container style(--_i < .75) {background-color: #7AB317}
  @container style(--_i < .5 ) {background-color: #F27435}
  @container style(--_i < .25) {background-color: #F04155}
}

Conclusion

I hope this article and the previous one give you a good overview of what modern CSS looks like. We are far from the era of simply setting color: red and margin: auto. Now, it’s a lot of variables, calculations, conditional logic, and more!

Article Series

]]>
https://frontendmasters.com/blog/custom-progress-element-using-the-attr-function/feed/ 0 5537
How to Use attr() in CSS for Columns, Colors, and Font-Size https://frontendmasters.com/blog/how-to-use-attr-in-css-for-columns-colors-and-font-size/ https://frontendmasters.com/blog/how-to-use-attr-in-css-for-columns-colors-and-font-size/#comments Tue, 25 Feb 2025 19:04:17 +0000 https://frontendmasters.com/blog/?p=5224 I’ve personally put “advanced attr() usage” on my CSS wishlists for years and years. All the sudden we’re seeing support for it start to drop! Props to the Chrome gang and others for shipping and highlighting this wonderfulness. I’m avoiding being entirely comprehensive about this feature in this post, so it’s worth perusing other coverage:

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 classic use cases.

The Deal

The value of attr()used to be always a string, which made the value pretty much only useful for the content property and certain niche text effects.

div::after {
  content: attr(data-title); 
}

Even if you put something like data-number="10", you could never get a proper number 10 to use in CSS. The same with like data-size="3rem", no chance of actually getting 3rem to actually use.

Now you can! You just have to declare what type it will be.

div {
  font-size: attr(data-font-size type(<length>));
  grid-column-start: attr(data-column-start type(<integer>));

  /* example with a "fallback" */
  color: attr(data-color type(<color>), black);
}

This usage is Chrome-only as I write/publish here, but that will change over time.

Types

<string>
<angle>
<color>
<custom-ident>
<integer>
<length>
<length-percentage>
<number>
<percentage>
<resolution>
<time>
<transform-function>

Using attr() for Grid Control

Here’s an example where:

  • data-row explicitly sets what row an element is on
  • data-columns explicitly sets how many columns an element should span
  • data-column-start and data-column-end set on where a column should start or end

That’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.

Using attr() for Colors

Just naming a color you want to use is nice!

This example highlights a nicety of having the additional “layer” of CSS to handle things, as opposed to very direct inline styles. Here I’m not just setting one color, but I’m using the color given for the actual color, then taking that same color and color-mix()ing in some black to use as the background color.

To highlight the control you have even more, we could use min() and max() 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.

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’re converting the color to oklch() and ensure the “l” part (lightness) has a minimum value of 0.9 (with max(l, 0.9)) to ensure that.

Using attr() for Font Sizes

Setting a data-font-size is easy peasy. But here I’ll jump forward to the “extra control” part right away. Perhaps your design system has strong rules about font sizing and you only allow increments of 5px. Using CSS round() we could make that happen with this approach.

Others?

  • How about data-gap as a utility to just change the gap, but be able to round it to particular values in a design system?
  • How about automatic view-transition-names like Bramus did up.
  • One-off borders with a data-border seems like nice control to offer. Or even individual borders. Or individual parts of individual borders like data-border-bottom-width.
  • 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’t seem possible otherwise, like…
[data-button-color] {
  --button-color: attr(data-button-color type(<color>));

  button {
    color: var(--button-color);
  }
}

With that it seems like you could do stuff like data-size-of-close-button on a <dialog> and then access that informaton wherever you implement the close button within there.

Why always use data-*?

You don’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 gap 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 data-* namespace was created just for this reason, so we might as well use it. Plus you get the JavaScript dataset property to use for free if you do.

ok bye and please shower me with more ideas for this.

]]>
https://frontendmasters.com/blog/how-to-use-attr-in-css-for-columns-colors-and-font-size/feed/ 5 5224