Using Custom Properties instead of Utility Classes : Part 2

Some 18 months ago I wrote about using custom properties instead of utility classes for often used styling like margin and padding.

Though it feels like I’ve not been using custom properties that long, and still probably don’t use them as extensively as I should, the version of the code I use today seems to have taken a few steps forward that’s worthy of sharing.

Here’s the code I use today:

:where(*){

  --margin-top:0;
  --margin-bottom:0;
  --padding-top:0;
  --padding-bottom:0;
  
  margin-top:clamp(
    calc((var(--margin-top) / 2) * 1px), 
    calc((var(--margin-top) / 14) * 1vw),
    calc(var(--margin-top) * 1px));
        
  margin-bottom:clamp(
    calc((var(--margin-bottom) / 2) * 1px),
    calc((var(--margin-bottom) / 14) * 1vw),
    calc(var(--margin-bottom) * 1px));
        
  padding-top:clamp(
    calc((var(--padding-top) / 2) * 1px),
    calc((var(--padding-top) / 14) * 1vw),
    calc(var(--padding-top) * 1px));
        
  padding-bottom:clamp(
    calc((var(--padding-bottom) / 2) * 1px),
    calc((var(--padding-bottom) / 14) * 1vw),
    calc(var(--padding-bottom) * 1px));

}


:where([style*="--margin-block"],[style*="--padding-block"]){

  --margin-block:0;
  --padding-block:0;

  margin-block:clamp(
    calc((var(--margin-block) / 2) * 1px),
    calc((var(--margin-block) / 14) * 1vw),
    calc(var(--margin-block) * 1px));

  padding-block:clamp(
    calc((var(--padding-block) / 2) * 1px),
    calc((var(--padding-block) / 14) * 1vw),
    calc(var(--padding-block) * 1px));

}

What’s changed?

As you’ll see from my original example code below; not that much. But there are a few significant changes that make the code far more usable.

<style>
.box {
 
  /* To avoid subsequent elements inheriting from previously rendered siblings/ancestors */
  --margin-top:0;
  --margin-bottom:0;
  --padding-top:0;
  --padding-bottom:0;
  
  margin-top:clamp(
    calc((var(--margin-top) / 2) * 1px), 
    calc((var(--margin-top) / 14) * 1vw),
    calc(var(--margin-top) * 1px));
        
  margin-bottom:clamp(
    calc((var(--margin-bottom) / 2) * 1px),
    calc((var(--margin-bottom) / 14) * 1vw),
    calc(var(--margin-bottom) * 1px));
        
  padding-top:clamp(
    calc((var(--padding-top) / 2) * 1px),
    calc((var(--padding-top) / 14) * 1vw),
    calc(var(--padding-top) * 1px));
        
  padding-bottom:clamp(
    calc((var(--padding-bottom) / 2) * 1px),
    calc((var(--padding-bottom) / 14) * 1vw),
    calc(var(--padding-bottom) * 1px));

}

</style>

<div class="box" style="--padding-top:20px;">Text</div>

Logical properties

The most obvious addition is the use of logical properties. Using these rather than having to define each dimension separately or having to write code like margin:0 auto; makes my code more succinct, flexible and readable.

Unfortunately, when creating custom properties for these, I couldn’t combine these with the existing physical values within a single selector. The reason for this is; imagine that we wish to set the margin-top to 10px, yet we have our default --margin-block property set to 0. If the margin-block code is positioned below the margin-top code, this resets margin-top to 0. Switching the order of the code only works to cause the same issue in reverse.

While our code ensures that when we set the logical property this takes precedent, it also means we can’t mix logical and physical values when using custom properties.

:where(selectors)

:where(*){

:where([style*="--margin-block"],[style*="--padding-block"]){

In my previous code I utilised a class selector (.box) in addition to inline style attributes, making things more complicated than necessary.

Since that original post it became obvious that using attribute selectors would be a more streamlined way of targeting these elements, without the need for an entirely new class to be added to each element too.

Wrapping these selectors within the :where pseudo-class also brings the handy benefit of setting the specificity of the selectors to 0. The advantage of this is if I choose to reset these values elsewhere in my CSS this new code will have the higher specificity and then take precedence when rendered.

For the physical values (top, bottom) you’ll see that rather than use attribute selectors, I’ve used a wildcard selector (again using :where to ensure 0 specificity). Previously this code did use attribute selectors, but this required that my custom properties could only be set within inline style elements. By utilising the wildcard it’s unlocked the ability to also use the custom-properties within my CSS too.

As there’s issues mixing physical and logical properties within the same selector it does leave us unable to combine custom properties with logical properties within our CSS.

Anything else?

I’m not sure if I’ve reached the peak of this code yet. If I were to find out that it’s hugely inefficient, then it might need to be binned off… but I hope not as it’s proved quite helpful, especially on larger websites.

In terms of improvements, I might think benefit? I’d like to see if there’s any advantage to adjusting the scaling a little more intelligently than just dividing the custom property by 14. Also, I’d like to see if there’s a way that smaller numbers are divided by less than 2… if at all. This would allow the use of smaller units without the issue that spacing gets too small on mobile.

But then you could argue why use the custom properties in this instance anyway. So, who knows what might be next, maybe some new CSS feature is on the horizon that will supplant it all.


We'd love to hear from you!

If you think Bronco has the skills to take your business forward then what are you waiting for?

Get in Touch Today!

Discussion

Add a Comment

Get in touch