CSS utility classes: Your library of extendable styles
Brian Neville-O'Neill
Posted on October 23, 2019
Written by Russell Bishop✏️
Building with CSS utility classes is an incredible boost to productivity and organization. It allows you to define a library of property: value
pairs that power all your styles, managed from a single directory.
In this article, we’re going to cover:
- What CSS utility classes are and why you should be using them
- A quick demo in a popular framework
- How to build your own super-powered utility library
What is a utility class?
Utility classes are self-descriptive, single-purpose CSS classes:
.flex {
display: flex;
}
Developers use these functional classes to build without writing additional CSS because if the style is in the library, you can use it over and over and over…
<aside class="flex flex-column bg-black">
<div class="flex align-center justify-center">
<img src="#" />
</div>
<div class="flex flex-column">
<h1>Jamie Thrift</h1>
<p class="flex align-center">
<svg>…</svg>
Head of HR
</p>
</div>
</aside>
The classes tell us exactly what they do, so developers can visualize how these elements will be laid out instead of needing to scan through the underlying CSS.
How are utility classes put together?
Utility classes are generated for you as part of a framework. Some of these popular tools and frameworks provide lots of styles out of the box, so you can just use class="padding-10"
and be confident that the style already exists. Others give you the tools to define only the utilities you require for your project.
By configuring your library of styles in one place, you avoid littering your codebase with outliers and losing sight of what is new CSS and what has already been written somewhere else.
Working with utilities massively increases the organization of your project and promotes predictability and consistency for both developers and end users.
…just like a design system!
This mode of thinking has also materialized in the UI design camp, where there is an emergent practice of defining, reusing, and maintaining a central library of styles. These styles are ordinarily found in the Styleguide section of a Design System.
Styleguides mostly consist of the foundations of color, typography, spacing, grids, and iconography. These low-level fundamentals are a set of rules that designers follow to create consistent work.
Utility-first CSS frameworks
There are quite a few frameworks, libraries and tools that have gained popularity in this space over the years. Here is a quick run-down of the history of some well-known options as they were released, alongside some case studies from developers who have taken a utility-first approach.
2013
Basscss
- Tagline: “Lightning-fast modular CSS with no side effects”
- GitHub: basscss/basscss
Tachyons
- Tagline: “Quickly build and design new UI without writing CSS”
- GitHub: tachyons-css/tachyons
2014
Beard
- Tagline: “A CSS framework for people with better things to do”
- Further reading: “Utility-first CSS: Ridiculously fast front-end development for almost every design,” by Sascha Wolff
- GitHub: monarkee/beard
2015
turretcss
- Tagline: “Designed to make responsive, accessible, and scalable CSS development simple and predictable”
- Further reading: “Building and shipping functional CSS,” by Cole Peters
- GitHub: turretcss/turretcss
2017
Tailwind CSS
- Tagline: “A utility-first CSS framework for rapidly building custom designs.”
- Further viewing: “Tailwind CSS – Rebuilding Twitter,” by Andre Madarang
- GitHub: tailwindcss/tailwindcss
2018
stemCSS
- Tagline: “Build the stem — don’t repeat yourself, don’t unset yourself”
- Further reading: “On Moving to a Utility-first CSS Framework,” by Debra Ohayon
- GitHub: wearelighthouse/stemCSS
Tailwind CSS | Tachyons | iotaCSS | |
Best feature | Superb documentation | Pre-composed components for rapid development | Intuitive organization of components, objects, and utilities |
Worst feature | JavaScript configuration | Hard to customize | Each set of utilities is a package |
Class syntax |
.text-2xl
or…
.propertyalias-valuealias
|
.f-headline
or…
.propertyalias-valuealias
|
.o-type-small
or…
.namespace-propertyalias-valuealias
|
Responsive syntax |
.md:flex-shrink-0
or…
.breakpointalias:propertyalias-valuealias
|
.w-100-m
or…
.propertyalias-valuealias-breakpointalias
|
u-text-right@sm
or…
.namespace-propertyalias-valuealias-breakpointalias
|
Hover/focus syntax |
.focus:outline-none
or…
.state:propertyalias-valuealias
|
.hover-orange
or…
.state-propertyalias-valuealias
Limited combinations
|
.u-color-orange@hover
or…
.namespace-propertyalias-valuealias-state
|
Built with | JavaScript (config) CSS | CSS | SCSS |
Putting utility classes into practice
Let’s build something simple to see how these utility classes work. Here, we’re going to replicate the author component you’ll probably recognize from Stack Overflow.
I’m going to use the syntax from Tailwind CSS for this example.
codepen
<div class="p-3">
<p class="text-gray-600">answered <span title="2017-08-07 08:46:14Z">Aug 7 '17 at 8:46</span></p>
<figure class="flex items-center mt-3">
<img class="w-10 h-10" src="https://assets-us-01.kc-usercontent.com/525f1d2d-c241-00a9-f169-8b2061aeb3da/e53a3a2b-f539-4ab9-a3ec-7cf7a2b6a664/aleksandra_zamojc.png" alt="Photo of Konrad Albrecht" />
<figcaption class="ml-2">
<p>
<a class="text-blue-600" href="/user">Konrad Albrecht</a>
</p>
<p class="flex items-center mt-1">
<span class="font-bold text-gray-800" title="reputation score">138</span>
<span class="rounded-full w-2 h-2 bg-orange-400 ml-2"></span>
<span class="ml-1 text-gray-600" title="badge count">10</span>
</p>
</figcaption>
</figure>
</div>
Nothing to name
“Once you name it, you get attached to it.”
Notice that none of this markup required us to name anything — because naming things is hard. We haven’t had to commit to a structure like .user
or .author
… which allows us to build our components faster and will leave us with less to maintain. If we later add another component that includes an author, we don’t have to rethink the naming scheme.
Predictability
Truth be told, I had only quickly glanced at the Tailwind CSS documentation before starting to build that component, and I only had to look up two of the classes that I wasn’t able to guess (.rounded-full
and items-center
).
This means that building a component can happen in one file instead of flipping between two.
What are the potential downsides?
As with any approach you adopt in development, there are always compromises made. Let’s explore a few of the complications at hand.
Long combinations of classes
A never-ending, wrapped line of text is very hard to decipher. When we write normal CSS, we have a few techniques that help us to scan a set of properties:
- One rule per line
- Indentation
- Rules maintain a property-sort-order
- Breakpoints can be nested with pre-/post-processing
When we relocate that long list of styles back into our markup, we tend to abandon the above as it would make our HTML incredibly lengthy. This leads to exceedingly long strings of classes that are difficult to make sense of — or even lint.
Try finding the weight property in here that you’d like to adjust:
<a href="https://myurl.com/" class="bgcolor-blue bg-color-darkblue@hover bg-color-darkblue@focus radius-tlbr-3 radius-trbl-12 font-europa color-light-grey color-white@hover color-white@focus weight-medium shadow-black-thin padv-3 padh-4 whitespace-nowrap size-6rem line-thin pos-relative top-3@hover top-3@focus">Incredible pace in this place</a>
It’s worth mentioning this example doesn’t even have any responsive styles — if it were to change properties at one or two breakpoints, this could feasibly double in length.
File size management
If we go all in, some frameworks generate a class for every possible value for every property for every breakpoint and every state. That’s a huge dumpster of CSS, of which you are probably using 1 percent. Frameworks know this, and they have their own ways of dealing with it.
Tailwind CSS (350kB minified) has a section on controlling file size that recommends setting up PurgeCSS.
Purge will scan your project files to find the classes that you have used and delete the rest from your compiled CSS file. This means an extra step in your build and perhaps even a delay when running your scripts if your codebase is considerably large. There may also be inaccuracies where your programming language obscures your class names (class="bgcolor-${foo}"
) to worry about.
Complex selectors
Many of CSS’s most helpful shortcuts are found in its powerful selectors. Consider, for example, a form section that fades away when the user is not interacting with it:
.fieldset:not(:focus-within):not(:hover) {
opacity: .5;
}
Or just working with pseudo-elements:
.title:after {
position: absolute;
bottom: -2px;
left: 0;
right: 0;
border-bottom: 2px solid orange;
content: '';
}
Or even something as simple as a zebra-striped table row:
.table-row:nth-child(odd) {
background-color: grey;
}
To use (fairly common!) styles like these, you would likely end up maintaining some custom CSS alongside your utility framework, or using components in iotaCSS.
Losing the cascade
Not only that, but being able to target using the cascade is also a huge time-saver when laying out elements — like deploying everybody’s favorite lobotomized owl selector:
.margin-top-siblings-2 > * + * {
margin-top: 2rem;
}
These time-saving styles are not provided by default in any of the frameworks I’ve seen, which means you’ll end up adding a lot more classes in you markup to compensate.
Building your own utility framework
Having provided both sides of the story on utility classes, we can definitely see room for improvement in the way existing frameworks are provided.
To build our own utility framework in SCSS, we’ll stick to the following principles:
- Only generate the classes you need
- Reduce waste in compiled CSS
- Natural separation of types of styling
To achieve that, we’re going to take advantage of:
-
%placeholders
and@extend
- Namespaced objects and components (
.u-
,.o-
,.c-
) - Loops and lists
Why namespace?
Using a small namespace for your class types, you have a structured way to indicate what a class does depending on its type_._ Most of the frameworks we mentioned earlier do not do this, as they are written to cater for utility only.
We will use namespaces in all of the following examples.
Placeholders to define styles
Placeholders are “ghost” definitions made in SCSS to define a style. Their unique feature is that they do not compile unless you reference them somewhere. It’s as if you defined a $variable
but never used it.
// Define the placeholder…
%u-display-flex {
display: flex;
}
// This will not compile, until…
.u-flex {
@extend %u-display-flex;
}
// Now we have our utility class ready to use…
.u-flex {
display: flex;
}
Because placeholders won’t show up until we want them to, we can also go ahead and make a placeholder for every breakpoint and state variation on a style.
@extend
to apply our placeholders
With our placeholders defined, we can now decide how we want to use them. By using @extend
(as opposed to Tailwind CSS’s @apply
), we also reduce the amount of repetition in our CSS.
As an example, let’s imagine that lots of our components use a specific border. Our CSS is unnecessarily large if it reads:
.c-sidebar {
border: 1px solid #222;
}
.c-card {
border: 1px solid #222;
}
.c-header {
border: 1px solid #222;
}
When we are consistently using low-specificity classes, we can group these styles together and DRY out the end result:
.c-sidebar {
@extend %border-grey;
}
.c-card {
@extend %border-grey;
}
.c-header {
@extend %border-grey;
}
// Which compiles to…
.c-sidebar,
.c-card,
.c-header {
border: 1px solid #222;
}
Our components can @extend
any styles we have in our library, and by referencing a placeholder, we know we’re not creating a wasteful new style definition. Let’s rewrite that messy component example from earlier:
.c-featured-link {
@extend
%u-pos-relative,
%u-padv-3,
%u-padh-4,
%u-bgcolor-blue,
%u-shadow-black-thin,
%u-radius-tlbr-3,
%u-radius-trbl-12,
%u-weight-medium,
%u-size-6rem,
%u-line-thin,
%u-font-europa,
%u-color-light-grey,
%u-whitespace-nowrap;
}
Nesting inside components
We can also hugely improve the readability of these components by nesting our breakpoints and states.
.c-featured-link {
// …
&:hover,
&:focus {
@extend
%u-top-3,
%u-bg-color-darkblue,
%u-color-white;
}
}
Building the tools we’ll need
Finally, I’ll walk you through the tools you’ll need in your SCSS to pull off the above. I’m going to describe how they work first so you can rebuild them to your own liking, and then offer a link to a framework that already has the complete set working.
Make Placeholder
@mixin make-placeholder($utility-name, $properties) {
// No breakpoint
%#{$utility-name} {
@each $property, $value in $properties {
#{$property}: $value;
}
}
// Every breakpoint
@each $breakpoint-key, $breakpoint-value in $global-breakpoints {
%#{$utility-name + $global-breakpoint-separator + $breakpoint-key} {
@include breakpoint($breakpoint-key) {
@each $property, $value in $properties {
#{$property}: $value;
}
}
}
}
}
The mixin above first creates a placeholder for each property sent in the $properties
argument. Then, we loop through all the breakpoints we’ve defined elsewhere in the $global-breakpoints
map to ensure we have breakpoint placeholders, too.
This will provide us with placeholders as follows:
@media (min-width: 600px) {
%u-bgcolor-primary\@medium {
background-color: blue;
}
}
We escape the @ symbol (our breakpoint and state identifier) as it’s an invalid character; you’re welcome to choose your own separator character to replace it.
Make Class from Placeholder
@mixin make-class-from-placeholder($utility-name, $properties) {
.#{$utility-name} {
@extend %#{$utility-name};
}
}
As we need to (in the next mixin), create a utility class directly from our new %placeholder
.
Make Utility
@mixin make-utility($args) {
$class: map-use(
$args,
class,
false
);
$args: map-remove($args, class);
// If 'alias' key exists in $args, use that for the placeholder-name,
// otherwise use the key and value of the first property in $args
$utility-name: map-use(
$args,
alias,
first(map-keys($args)) + '-' + first(map-values($args))
);
$utility-name: 'u-' + $utility-name;
$properties: map-remove($args, alias);
@include make-placeholder($utility-name, $properties);
@if ($class) {
@include make-class-from-placeholder($utility-name, $properties);
}
}
The last step is to support aliases, which allows you to decide if you prefer to go letter for letter in your class names (justify-content-space-between
) or if you prefer a tiny shortcut (jc-sb
).
It’s completely up to you — the longer name means that any developer can jump in and will know the pattern immediately, but the shorter name is easier to scan for and faster to type.
With the alias decided (or left blank), we then check if you want to use Make Class and complete the placeholder.
Putting it all together with loops and control directives
To get the most our of our placeholder-building, you’ll want to list properties and values to add to your library.
Here are a few helpful loops to get you on your way:
@each $wrap in (nowrap, wrap, wrap-reverse) {
@include make-utility((
alias: 'fw-' + $wrap,
flex-wrap: $wrap
));
}
@each $property in (translateX, translateY) {
@each $value in (-100, -50, 0, 50, 100) {
@include make-utility((
alias: $property + '-' + $value + $global-unit-percent,
transform: $property + '(' + $value + '%)'
));
}
}
@each $property in (margin-top, margin-right, margin-bottom, margin-left) {
@for $value from 1 through 30 {
@include make-utility((
$property: #{$value + 'rem'};
));
}
}
If you’d like to quick-start your own framework, you can grab the latest version of stemCSS.
Summary
Recent tools and frameworks have rocketed the idea of utility classes into the mainstream, and they are reaping the benefits of their exceptional documentation (hat-tip to Tailwind CSS). It’s clear that utility classes have shifted the landscape for how developers use low-specificity CSS classes as a methodology for consistent styling.
What will the future of stylesheets throw at us next?
Editor's note: Seeing something wrong with this post? You can find the correct version here.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
Try it for free.
The post CSS utility classes: Your library of extendable styles appeared first on LogRocket Blog.
Posted on October 23, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.