Tag: Utility

Efficient Infinite Utility Helpers Using Inline CSS Custom Properties and calc()

I recently wrote a very basic Sass loop that outputs several padding and margin utility classes. Nothing fancy, really, just a Sass map with 11 spacing values, looped over to create classes for both padding and margin on each side. As we’ll see, this works, but it ends up a pretty hefty amount of CSS. We’re going to refactor it to use CSS custom properties and make the system much more trim.

Here’s the original Sass implementation:

$  space-stops: (   '0': 0,   '1': 0.25rem,   '2': 0.5rem,   '3': 0.75rem,   '4': 1rem,   '5': 1.25rem,   '6': 1.5rem,   '7': 1.75rem,   '8': 2rem,   '9': 2.25rem,   '10': 2.5rem, );  @each $  key, $  val in $  space-stops {   .p-#{$  key} {     padding: #{$  val} !important;   }   .pt-#{$  key} {     padding-top: #{$  val} !important;   }   .pr-#{$  key} {     padding-right: #{$  val} !important;   }   .pb-#{$  key} {     padding-bottom: #{$  val} !important;   }   .pl-#{$  key} {     padding-left: #{$  val} !important;   }   .px-#{$  key} {     padding-right: #{$  val} !important;     padding-left: #{$  val} !important;   }   .py-#{$  key} {     padding-top: #{$  val} !important;     padding-bottom: #{$  val} !important;   }    .m-#{$  key} {     margin: #{$  val} !important;   }   .mt-#{$  key} {     margin-top: #{$  val} !important;   }   .mr-#{$  key} {     margin-right: #{$  val} !important;   }   .mb-#{$  key} {     margin-bottom: #{$  val} !important;   }   .ml-#{$  key} {     margin-left: #{$  val} !important;   }   .mx-#{$  key} {     margin-right: #{$  val} !important;     margin-left: #{$  val} !important;   }   .my-#{$  key} {     margin-top: #{$  val} !important;     margin-bottom: #{$  val} !important;   } } 

This very much works. It outputs all the utility classes we need. But, it can also get bloated quickly. In my case, they were about 8.6kb uncompressed and under 1kb compressed. (Brotli was 542 bytes, and gzip came in at 925 bytes.)

Since they are extremely repetitive, they compress well, but I still couldn’t shake the feeling that all these classes were overkill. Plus, I hadn’t even done any small/medium/large breakpoints which are fairly typical for these kinds of helper classes.

Here’s a contrived example of what the responsive version might look like with small/medium/large classes added. We’ll re-use the $ space-stops map defined previously and throw our repetitious code into a mixin

@mixin finite-spacing-utils($  bp: '') {     @each $  key, $  val in $  space-stops {         .p-#{$  key}#{$  bp} {             padding: #{$  val} !important;         }         .pt-#{$  key}#{$  bp} {             padding-top: #{$  val} !important;         }         .pr-#{$  key}#{$  bp} {             padding-right: #{$  val} !important;         }         .pb-#{$  key}#{$  bp} {             padding-bottom: #{$  val} !important;         }         .pl-#{$  key}#{$  bp} {             padding-left: #{$  val} !important;         }         .px-#{$  key}#{$  bp} {             padding-right: #{$  val} !important;             padding-left: #{$  val} !important;         }         .py-#{$  key}#{$  bp} {             padding-top: #{$  val} !important;             padding-bottom: #{$  val} !important;         }          .m-#{$  key}#{$  bp} {             margin: #{$  val} !important;         }         .mt-#{$  key}#{$  bp} {             margin-top: #{$  val} !important;         }         .mr-#{$  key}#{$  bp} {             margin-right: #{$  val} !important;         }         .mb-#{$  key}#{$  bp} {             margin-bottom: #{$  val} !important;         }         .ml-#{$  key}#{$  bp} {             margin-left: #{$  val} !important;         }         .mx-#{$  key}#{$  bp} {             margin-right: #{$  val} !important;             margin-left: #{$  val} !important;         }         .my-#{$  key}#{$  bp} {             margin-top: #{$  val} !important;             margin-bottom: #{$  val} !important;         }     } }  @include finite-spacing-utils;  @media (min-width: 544px) {     @include finite-spacing-utils($  bp: '_sm'); }  @media (min-width: 768px) {     @include finite-spacing-utils($  bp: '_md'); }  @media (min-width: 1024px) {     @include finite-spacing-utils($  bp: '_lg'); }  

That clocks in at about 41.7kb uncompressed (and about 1kb with Brotli, and 3kb with gzip). It still compresses well, but it’s a bit ridiculous.

I knew it was possible to reference data-* attributes from within CSS using the [attr() function, so I wondered if it was possible to use calc() and attr() together to create dynamically-calculated spacing utility helpers via data-* attributes — like data-m="1" or data-m="1@md" — then in the CSS to do something like margin: calc(attr(data-m) * 0.25rem) (assuming I’m using a spacing scale incrementing at 0.25rem intervals). That could be very powerful.

But the end of that story is: no, you (currently) can’t use attr() with any property except the content property. Bummer. But in searching for attr() and calc() information, I found this intriguing Stack Overflow comment by Simon Rigét that suggests setting a CSS variable directly within an inline style attribute. Aha!

So it’s possible to do something like <div style="--p: 4;"> then, in CSS:

:root {   --p: 0; }  [style*='--p:'] {   padding: calc(0.25rem * var(--p)) !important; } 

In the case of the style="--p: 4;" example, you’d effectively end up with padding: 1rem !important;.

… and now you have an infinitely scalable spacing utility class monstrosity helper.

Here’s what that might look like in CSS:

:root {   --p: 0;   --pt: 0;   --pr: 0;   --pb: 0;   --pl: 0;   --px: 0;   --py: 0;   --m: 0;   --mt: 0;   --mr: 0;   --mb: 0;   --ml: 0;   --mx: 0;   --my: 0; }  [style*='--p:'] {   padding: calc(0.25rem * var(--p)) !important; } [style*='--pt:'] {   padding-top: calc(0.25rem * var(--pt)) !important; } [style*='--pr:'] {   padding-right: calc(0.25rem * var(--pr)) !important; } [style*='--pb:'] {   padding-bottom: calc(0.25rem * var(--pb)) !important; } [style*='--pl:'] {   padding-left: calc(0.25rem * var(--pl)) !important; } [style*='--px:'] {   padding-right: calc(0.25rem * var(--px)) !important;   padding-left: calc(0.25rem * var(--px)) !important; } [style*='--py:'] {   padding-top: calc(0.25rem * var(--py)) !important;   padding-bottom: calc(0.25rem * var(--py)) !important; }  [style*='--m:'] {   margin: calc(0.25rem * var(--m)) !important; } [style*='--mt:'] {   margin-top: calc(0.25rem * var(--mt)) !important; } [style*='--mr:'] {   margin-right: calc(0.25rem * var(--mr)) !important; } [style*='--mb:'] {   margin-bottom: calc(0.25rem * var(--mb)) !important; } [style*='--ml:'] {   margin-left: calc(0.25rem * var(--ml)) !important; } [style*='--mx:'] {   margin-right: calc(0.25rem * var(--mx)) !important;   margin-left: calc(0.25rem * var(--mx)) !important; } [style*='--my:'] {   margin-top: calc(0.25rem * var(--my)) !important;   margin-bottom: calc(0.25rem * var(--my)) !important; }  

This is a lot like the first Sass loop above, but there’s no loop going 11 times — and yet it’s infinite. It’s about 1.4kb uncompressed, 226 bytes with Brotli, or 284 bytes gzipped.

If you wanted to extend this for breakpoints, the unfortunate news is that you can’t put the “@” character in CSS variable names (although emojis and other UTF-8 characters are strangely permitted). So you could probably set up variable names like p_sm or sm_p. You’d have to add some extra CSS variables and some media queries to handle all this, but it won’t blow up exponentially the way traditional CSS classnames created with a Sass for-loop do.

Here’s the equivalent responsive version. We’ll use a Sass mixin again to cut down the repetition:

:root {   --p: 0;   --pt: 0;   --pr: 0;   --pb: 0;   --pl: 0;   --px: 0;   --py: 0;   --m: 0;   --mt: 0;   --mr: 0;   --mb: 0;   --ml: 0;   --mx: 0;   --my: 0; }  @mixin infinite-spacing-utils($  bp: '') {     [style*='--p#{$  bp}:'] {         padding: calc(0.25rem * var(--p)) !important;     }     [style*='--pt#{$  bp}:'] {         padding-top: calc(0.25rem * var(--pt)) !important;     }     [style*='--pr#{$  bp}:'] {         padding-right: calc(0.25rem * var(--pr)) !important;     }     [style*='--pb#{$  bp}:'] {         padding-bottom: calc(0.25rem * var(--pb)) !important;     }     [style*='--pl#{$  bp}:'] {         padding-left: calc(0.25rem * var(--pl)) !important;     }     [style*='--px#{$  bp}:'] {         padding-right: calc(0.25rem * var(--px)) !important;         padding-left: calc(0.25rem * var(--px)) !important;     }     [style*='--py#{$  bp}:'] {         padding-top: calc(0.25rem * var(--py)) !important;         padding-bottom: calc(0.25rem * var(--py)) !important;     }     [style*='--m#{$  bp}:'] {         margin: calc(0.25rem * var(--m)) !important;     }     [style*='--mt#{$  bp}:'] {         margin-top: calc(0.25rem * var(--mt)) !important;     }     [style*='--mr#{$  bp}:'] {         margin-right: calc(0.25rem * var(--mr)) !important;     }     [style*='--mb#{$  bp}:'] {         margin-bottom: calc(0.25rem * var(--mb)) !important;     }     [style*='--ml#{$  bp}:'] {         margin-left: calc(0.25rem * var(--ml)) !important;     }     [style*='--mx#{$  bp}:'] {         margin-right: calc(0.25rem * var(--mx)) !important;         margin-left: calc(0.25rem * var(--mx)) !important;     }     [style*='--my#{$  bp}:'] {         margin-top: calc(0.25rem * var(--my)) !important;         margin-bottom: calc(0.25rem * var(--my)) !important;     } }  @include infinite-spacing-utils;  @media (min-width: 544px) {     @include infinite-spacing-utils($  bp: '_sm'); }  @media (min-width: 768px) {     @include infinite-spacing-utils($  bp: '_md'); }  @media (min-width: 1024px) {     @include infinite-spacing-utils($  bp: '_lg'); }  

That’s about 6.1kb uncompressed, 428 bytes with Brotli, and 563 with gzip.

Do I think that writing HTML like <div style="--px:2; --my:4;"> is pleasing to the eye, or good developer ergonomics… no, not particularly. But could this approach be viable in situations where you (for some reason) need extremely minimal CSS, or perhaps no external CSS file at all? Yes, I sure do.

It’s worth pointing out here that CSS variables assigned in inline styles do not leak out. They’re scoped only to the current element and don’t change the value of the variable globally. Thank goodness! The one oddity I have found so far is that DevTools (at least in Chrome, Firefox, and Safari) do not report the styles using this technique in the “Computed” styles tab.

Also worth mentioning is that I’ve used good old padding  and margin properties with -top, -right, -bottom, and -left, but you could use the equivalent logical properties like padding-block and padding-inline. It’s even possible to shave off just a few more bytes by selectively mixing and matching logical properties with traditional properties. I managed to get it down to 400 bytes with Brotli and 521 with gzip this way.

Other use cases

This seems most appropriate for things that are on a (linear) incremental scale (which is why padding and margin seems like a good use case) but I could see this potentially working for widths and heights in grid systems (column numbers and/or widths). Maybe for typographic scales (but maybe not).

I’ve focused a lot on file size, but there may be some other uses here I’m not thinking of. Perhaps you wouldn’t write your code in this way, but a critical CSS tool could potentially refactor the code to use this approach.

Digging deeper

As I dug deeper, I found that Ahmad Shadeed blogged in 2019 about mixing calc() with CSS variable assignments within inline styles particularly for avatar sizes. Miriam Suzanne’s article on Smashing Magazine in 2019 didn’t use calc() but shared some amazing things you can do with variable assignments in inline styles.


The post Efficient Infinite Utility Helpers Using Inline CSS Custom Properties and calc() appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , , , ,

Use CSS Clamp to create a more flexible wrapper utility

I like Andy’s idea here:

.wrapper {   width: clamp(16rem, 90vw, 70rem);   margin-left: auto;   margin-right: auto;   padding-left: 1.5rem;   padding-right: 1.5rem; }

Normally I’d just set a max-width there, but as Andy says:

This becomes a slight issue in mid-sized viewports, such as tablets in portrait mode, in long-form content, such as this article because contextually, the line-lengths feel very long.

So, on super large screens, you’ll get capped at 70rem (or whatever you think a good maximum is), and on small screens you’ll get full width, which is fine. But it’s those in-betweens that aren’t so great. I made a little demo to get a feel for it. This video makes it clear I think:

Direct Link to ArticlePermalink


The post Use CSS Clamp to create a more flexible wrapper utility appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

A Utility Class for Covering Elements

Big ol’ same to Michelle Barker here:

Here’s something I find myself needing to do again and again in CSS: completely covering one element with another. It’s the same CSS every time: the first element (the one that needs to be covered) has position: relative applied to it. The second has position: absolute and is positioned so that all four sides align to the edges of the first element.

.original-element {   position: relative; }  .covering-element {   position: absolute;   top: 0;   right: 0;   bottom: 0;   left: 0; }

I have it stuck in my head somehow that it’s “not as reliable” to use bottom and right and that it’s safer to set the top and left then do width: 100% and height: 100%. But I can’t remember why anymore—maybe it was an older browser thing?

But speaking of modernizing things, my favorite bit from Michelle’s article is this:

.overlay {   position: absolute;   inset: 0; }

The inset property is a Logical Property and clearly very handy here! Read the article for another trick involving CSS grid.

Direct Link to ArticlePermalink


The post A Utility Class for Covering Elements appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Building a Scalable CSS Architecture With BEM and Utility Classes

Maintaining a large-scale CSS project is hard. Over the years, we’ve witnessed different approaches aimed at easing the process of writing scalable CSS. In the end, we all try to meet the following two goals:

  1. Efficiency: we want to reduce the time spent thinking about how things should be done and increase the time doing things.
  2. Consistency: we want to make sure all developers are on the same page.

For the past year and a half, I’ve been working on a component library and a front-end framework called CodyFrame. We currently have 220+ components. These components are not isolated modules: they’re reusable patterns, often merged into each other to create complex templates.

The challenges of this project have forced our team to develop a way of building scalable CSS architectures. This method relies on CSS globals, BEM, and utility classes.

I’m happy to share it! 👇

CSS Globals in 30 seconds

Globals are CSS files containing rules that apply crosswise to all components (e.g., spacing scale, typography scale, colors, etc.). Globals use tokens to keep the design consistent across all components and reduce the size of their CSS.

Here’s an example of typography global rules:

/* Typography | Global */ :root {   /* body font size */   --text-base-size: 1em; 
   /* type scale */   --text-scale-ratio: 1.2;   --text-xs: calc((1em / var(--text-scale-ratio)) / var(--text-scale-ratio));   --text-sm: calc(var(--text-xs) * var(--text-scale-ratio));   --text-md: calc(var(--text-sm) * var(--text-scale-ratio) * var(--text-scale-ratio));   --text-lg: calc(var(--text-md) * var(--text-scale-ratio));   --text-xl: calc(var(--text-lg) * var(--text-scale-ratio));   --text-xxl: calc(var(--text-xl) * var(--text-scale-ratio)); } 
 @media (min-width: 64rem) { /* responsive decision applied to all text elements */   :root {     --text-base-size: 1.25em;     --text-scale-ratio: 1.25;   } } 
 h1, .text-xxl   { font-size: var(--text-xxl, 2.074em); } h2, .text-xl    { font-size: var(--text-xl, 1.728em); } h3, .text-lg    { font-size: var(--text-lg, 1.44em); } h4, .text-md    { font-size: var(--text-md, 1.2em); } .text-base      { font-size: 1em; } small, .text-sm { font-size: var(--text-sm, 0.833em); } .text-xs        { font-size: var(--text-xs, 0.694em); }

BEM in 30 seconds

BEM (Blocks, Elements, Modifiers) is a naming methodology aimed at creating reusable components.

Here’s an example:

<header class="header">   <a href="#0" class="header__logo"><!-- ... --></a>   <nav class="header__nav">     <ul>       <li><a href="#0" class="header__link header__link--active">Homepage</a></li>       <li><a href="#0" class="header__link">About</a></li>       <li><a href="#0" class="header__link">Contact</a></li>     </ul>   </nav> </header>
  • A block is a reusable component
  • An element is a child of the block (e.g., .block__element)
  • A modifier is a variation of a block/element (e.g., .block--modifier, .block__element--modifier).

Utility classes in 30 seconds

A utility class is a CSS class meant to do only one thing. For example:

<section class="padding-md">   <h1>Title</h1>   <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p> </section> 
 <style>   .padding-sm { padding: 0.75em; }   .padding-md { padding: 1.25em; }   .padding-lg { padding: 2em; } </style>

You can potentially build entire components out of utility classes:

<article class="padding-md bg radius-md shadow-md">   <h1 class="text-lg color-contrast-higher">Title</h1>   <p class="text-sm color-contrast-medium">Lorem ipsum dolor sit amet consectetur adipisicing elit.</p> </article>

You can connect utility classes to CSS globals:

/* Spacing | Global */ :root {   --space-unit: 1em;   --space-xs:   calc(0.5 * var(--space-unit));   --space-sm:   calc(0.75 * var(--space-unit));   --space-md:   calc(1.25 * var(--space-unit));   --space-lg:   calc(2 * var(--space-unit));   --space-xl:   calc(3.25 * var(--space-unit)); }  /* responsive rule affecting all spacing variables */ @media (min-width: 64rem) {   :root {     --space-unit:  1.25em; /* 👇 this responsive decision affects all margins and paddings */   } }
 /* margin and padding util classes - apply spacing variables */ .margin-xs { margin: var(--space-xs); } .margin-sm { margin: var(--space-sm); } .margin-md { margin: var(--space-md); } .margin-lg { margin: var(--space-lg); } .margin-xl { margin: var(--space-xl); }  .padding-xs { padding: var(--space-xs); } .padding-sm { padding: var(--space-sm); } .padding-md { padding: var(--space-md); } .padding-lg { padding: var(--space-lg); } .padding-xl { padding: var(--space-xl); }

A real-life example

Explaining a methodology using basic examples doesn’t bring up the real issues nor the advantages of the method itself.

Let’s build something together! 

We’ll create a gallery of card elements. First, we’ll do it using only the BEM approach, and we’ll point out the issues you may face by going BEM only. Next, we’ll see how Globals reduce the size of your CSS. Finally, we’ll make the component customizable introducing utility classes to the mix.

Here’s a look at the final result:

Let’s start this experiment by creating the gallery using only BEM:

<div class="grid">   <article class="card">     <a class="card__link" href="#0">       <figure>         <img class="card__img" src="/image.jpg" alt="Image description">       </figure> 
       <div class="card__content">         <h1 class="card__title-wrapper"><span class="card__title">Title of the card</span></h1> 
         <p class="card__description">Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, totam?</p>       </div> 
       <div class="card__icon-wrapper" aria-hidden="true">         <svg class="card__icon" viewBox="0 0 24 24"><!-- icon --></svg>       </div>     </a>   </article> 
   <article class="card"><!-- card --></article>   <article class="card"><!-- card --></article>   <article class="card"><!-- card --></article> </div>

In this example, we have two components: .grid and .card. The first one is used to create the gallery layout. The second one is the card component.

First of all, let me point out the main advantages of using BEM: low specificity and scope.

/* without BEM */ .grid {} .card {} .card > a {} .card img {} .card-content {} .card .title {} .card .description {} 
 /* with BEM */ .grid {} .card {} .card__link {} .card__img {} .card__content {} .card__title {} .card__description {}

If you don’t use BEM (or a similar naming method), you end up creating inheritance relationships (.card > a).

/* without BEM */ .card > a.active {} /* high specificity */ 
 /* without BEM, when things go really bad */ div.container main .card.is-featured > a.active {} /* good luck with that 😦 */ 
 /* with BEM */ .card__link--active {} /* low specificity */

Dealing with inheritance and specificity in big projects is painful. That feeling when your CSS doesn’t seem to be working, and you find out it’s been overwritten by another class 😡! BEM, on the other hand, creates some kind of scope for your components and keeps specificity low.

But… there are two main downsides of using only BEM:

  1. Naming too many things is frustrating
  2. Minor customizations are not easy to do or maintain

In our example, to stylize the components, we’ve created the following classes:

.grid {} .card {} .card__link {} .card__img {} .card__content {} .card__title-wrapper {} .card__title {} .card__description {} .card__icon-wrapper {} .card__icon {}

The number of classes is not the issue. The issue is coming up with so many meaningful names (and having all your teammates use the same naming criteria).

For example, imagine you have to modify the card component by including an additional, smaller paragraph:

<div class="card__content">   <h1 class="card__title-wrapper"><span class="card__title">Title of the card</span></h1>   <p class="card__description">Lorem ipsum dolor...</p>   <p class="card__description card__description--small">Lorem ipsum dolor...</p> <!-- 👈 --> </div>

How do you call it? You could consider it a variation of the .card__description element and go for .card__description .card__description--small. Or, you could create a new element, something like .card__small, .card__small-p, or .card__tag. See where I’m going? No one wants to spend time thinking about class names. BEM is great as long as you don’t have to name too many things.

The second issue is dealing with minor customizations. For example, imagine you have to create a variation of the card component where the text is center-aligned.

You’ll probably do something like this:

<div class="card__content card__content--center"> <!-- 👈 -->   <h1 class="card__title-wrapper"><span class="card__title">Title of the card</span></h1>   <p class="card__description">Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, totam?</p> </div> 
 <style>   .card__content--center { text-align: center; } </style>

One of your teammates, working on another component (.banner), is facing the same problem. They create a variation for their component as well:

<div class="banner banner--text-center"></div> 
 <style>   .banner--text-center { text-align: center; } </style>

Now imagine you have to include the banner component into a page. You need the variation where the text is aligned in the center. Without checking the CSS of the banner component, you may instinctively write something like banner banner--center in your HTML, because you always use --center when you create variations where the text is center-aligned. Not working! Your only option is to open the CSS file of the banner component, inspect the code, and find out what class should be applied to align the text in the center.

How long would it take, 5 minutes? Multiply 5 minutes by all the times this happens in a day, to you and all your teammates, and you realize how much time is wasted. Plus, adding new classes that do the same thing contributes to bloating your CSS.

CSS Globals and utility classes to the rescue

The first advantage of setting global styles is having a set of CSS rules that apply to all the components.

For example, if we set responsive rules in the spacing and typography globals, these rules will affect the grid and card components as well. In CodyFrame, we increase the body font size at a specific breakpoint; because we use “em” units for all margins and paddings, the whole spacing system is updated at once generating a cascade effect.

Spacing and typography responsive rules — no media queries on a component level 

As a consequence, in most cases, you won’t need to use media queries to increase the font size or the values of margins and paddings!

/* without globals */ .card { padding: 1em; } 
 @media (min-width: 48rem) {   .card { padding: 2em; }   .card__content { font-size: 1.25em; } } 
 /* with globals (responsive rules intrinsically applied) */ .card { padding: var(--space-md); }

Not just that! You can use the globals to store behavioral components that can be combined with all other components. For example, in CodyFrame, we define a .text-component class that is used as a “text wrapper.” It takes care of line height, vertical spacing, basic styling, and other things.

If we go back to our card example, the .card__content element could be replaced with the following:

<!-- without globals --> <div class="card__content">   <h1 class="card__title-wrapper"><span class="card__title">Title of the card</span></h1>   <p class="card__description">Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, totam?</p> </div> 
 <!-- with globals --> <div class="text-component">   <h1 class="text-lg"><span class="card__title">Title of the card</span></h1>   <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, totam?</p> </div>

The text component will take care of the text formatting, and make it consistent across all the text blocks in your project. Plus, we’ve already eliminated a couple of BEM classes.

Finally, let’s introduce the utility classes to the mix!

Utility classes are particularly useful if you want the ability to customize the component later on without having to check its CSS.

Here’s how the structure of the card component changes if we swap some BEM classes with utility classes:

<article class="card radius-lg">   <a href="#0" class="block color-inherit text-decoration-none">     <figure>       <img class="block width-100%" src="image.jpg" alt="Image description">     </figure> 
     <div class="text-component padding-md">       <h1 class="text-lg"><span class="card__title">Title of the card</span></h1>       <p class="color-contrast-medium">Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, totam?</p>     </div> 
     <div class="card__icon-wrapper" aria-hidden="true">       <svg class="icon icon--sm color-white" viewBox="0 0 24 24"><!-- icon --></svg>     </div>   </a> </article>

The number of BEM (component) classes has shrunk from 9 to 3:

.card {} .card__title {} .card__icon-wrapper {}

That means you won’t deal much with naming things. That said, we can’t avoid the naming issue entirely: even if you create Vue/React/SomeOtherFramework components out of utility classes, you still have to name the components.

All the other BEM classes have been replaced by utility classes. What if you have to make a card variation with a bigger title? Replace text-lg with text-xl. What if you want to change the icon color? Replace color-white with color-primary. How about aligning the text in the center? Add text-center to the text-component element. Less time thinking, more time doing!

Why don’t we just use utility classes?

Utility classes speed-up the design process and make it easier to customize things. So why don’t we forget about BEM and use only utility classes? Two main reasons:

By using BEM together with utility classes, the HTML is easier to read and customize.

Use BEM for:

  • DRY-ing the HTML from the CSS you don’t plan on customizing (e.g., behavioral CSS-like transitions, positioning, hover/focus effects),
  • advanced animations/effects.

Use utility classes for:

  • the “frequently-customized” properties, often used to create component variations (like padding, margin, text-alignment, etc.),
  • elements that are hard to identify with a new, meaningful class name (e.g., you need a parent element with a position: relative → create <div class="position-relative"><div class="my-component"></div></div>).

Example: 

<!-- use only Utility classes --> <article class="position-relative overflow-hidden bg radius-lg transition-all duration-300 hover:shadow-md col-6@sm col-4@md">   <!-- card content --> </article> 
 <!-- use BEM + Utility classes --> <article class="card radius-lg col-6@sm col-4@md">   <!-- card content --> </article>

For these reasons, we suggest that you don’t add the !important rule to your utility classes. Using utility classes doesn’t need to be like using a hammer. Do you think it would be beneficial to access and modify a CSS property in the HTML? Use a utility class. Do you need a bunch of rules that won’t need editing? Write them in your CSS. This process doesn’t need to be perfect the first time you do it: you can tweak the component later on if required. It may sound laborious “having to decide” but it’s quite straightforward when you put it to practice.

Utility classes are not your best ally when it comes to creating unique effects/animations.

Think about working with pseudo-elements, or crafting unique motion effects that require custom bezier curves. For those, you still need to open your CSS file.

Consider, for example, the animated background effect of the card we’ve designed. How hard would it be to create such an effect using utility classes?

The same goes for the icon animation, which requires animation keyframes to work:

.card:hover .card__title {   background-size: 100% 100%; } 
 .card:hover .card__icon-wrapper .icon {   animation: card-icon-animation .3s; } 
 .card__title {   background-image: linear-gradient(transparent 50%, alpha(var(--color-primary), 0.2) 50%);   background-repeat: no-repeat;   background-position: left center;   background-size: 0% 100%;   transition: background .3s; } 
 .card__icon-wrapper {   position: absolute;   top: 0;   right: 0;   width: 3em;   height: 3em;   background-color: alpha(var(--color-black), 0.85);   border-bottom-left-radius: var(--radius-lg);   display: flex;   justify-content: center;   align-items: center; } 
 @keyframes card-icon-animation {   0%, 100% {     opacity: 1;     transform: translateX(0%);   }   50% {     opacity: 0;     transform: translateX(100%);   }   51% {     opacity: 0;     transform: translateX(-100%);   } }

Final result

Here’s the final version of the cards gallery. It also includes grid utility classes to customize the layout.

File structure

Here’s how the structure of a project built using the method described in this article would look like:

project/ └── main/     ├── assets/     │   ├── css/     │   │   ├── components/     │   │   │   ├── _card.scss     │   │   │   ├── _footer.scss     │   │   │   └── _header.scss     │   │   ├── globals/     │   │   │   ├── _accessibility.scss     │   │   │   ├── _breakpoints.scss     │   │   │   ├── _buttons.scss     │   │   │   ├── _colors.scss     │   │   │   ├── _forms.scss     │   │   │   ├── _grid-layout.scss     │   │   │   ├── _icons.scss     │   │   │   ├── _reset.scss     │   │   │   ├── _spacing.scss     │   │   │   ├── _typography.scss     │   │   │   ├── _util.scss     │   │   │   ├── _visibility.scss     │   │   │   └── _z-index.scss     │   │   ├── _globals.scss     │   │   ├── style.css     │   │   └── style.scss     │   └── js/     │       ├── components/     │       │   └── _header.js     │       └── util.js     └── index.html

You can store the CSS (or SCSS) of each component into a separate file (and, optionally, use PostCSS plugins to compile each new /component/componentName.css file into style.css). Feel free to organize the globals as you prefer; you could also create a single globals.css file and avoid separating the globals in different files.

Conclusion

Working on large-scale projects requires a solid architecture if you want to open your files months later and don’t get lost. There are many methods out there that tackle this issue (CSS-in-JS, utility-first, atomic design, etc.).

The method I’ve shared with you today relies on creating crosswise rules (globals), using utility classes for rapid development, and BEM for modular (behavioral) classes.

You can learn in more detail about this method on CodyHouse. Any feedback is welcome!

The post Building a Scalable CSS Architecture With BEM and Utility Classes appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]