Atomic Styles. Semantic Markup.

A case for variables and mixins.

Adam Thompson
4 min readApr 6, 2020

When starting a new project or feature it’s a good idea to write semantic markup first, then style it the way you want. Building markup-first ensures that your product will make sense, and be accessible to those using keyboard navigation or screen-readers. Once your markup is in place, you can then start styling the interface.

Ideally, you won’t be creating every element from scratch. A sufficiently mature component library will contain at least the most common interface elements like buttons, cards, and inputs. By leveraging these pre-built elements you can avoid writing additional redundant markup or styling.

Generally in the end, most of these repeatable elements will be independent JavaScript components, comprised of prepackaged, tightly coupled markup and styling to be re-used throughout the project.

These components are the main building blocks—the molecules—of your interface. These molecules in turn are made up of atoms—the foundational styles of your UI. This includes colors, fonts, sizes, spacing distances for padding and margin, and more.

Until this point, it hasn’t mattered how you apply atomic styles to components. You can achieve the same final JavaScript component by using CSS variables, or using utility classes in a functional CSS framework like Tailwind or Tachyons. I would personally advocate for writing CSS (or a LESS or SASS) over a functional class framework. But first let’s look at what a functional framework can get us.

Using a functional CSS framework is pretty easy. Once you’ve written your markup, you add classes to each element, which define how it should look.

The advantages of using functional classes are that it 1) gives clear, predictable, functional results, and 2) enforces consistency by removing options. While I appreciate these advantages, I feel that neither of these is fully accurate, or exclusive enough to avoid writing your own custom style sheets.

Predictability

One advantage of many utility class libraries is that it gives predictable results—applying a class does the same thing every time. There are still classes however that won’t behave predictably, or won’t work with/without other classes. The fact that they’re not in a cascading style sheet makes it ambiguous which will take precedence. They abstract away the context of cascading styles and can make it more difficult to predict behavior.

Pop quiz: using Tailwind, what will the display rule be for this div?:

<div class="inline grid flex">Hello world</div>

The answer is display: grid. Why? Because .grid is defined last in Tailwind’s stylesheet cascade. Now obviously this is a contrived example, but it shows that these utility classes aren’t always predictable. A less contrived example might be:

<div class="justify-center">Hello world</div>

You’d think that the class would center the text, but it doesn’t. You need to apply flex or grid in order for the class to take effect. Having a class depend on other classes no longer makes it functional, and harder to learn.

How would one know that these classes are dependent? It is written in the docs that you need flex, but you can also learn what CSS attributes they affect, and how that attribute interacts with other attributes. Now that we’re in CSS land, and know these classes aren’t purely functional, let’s just write our styles in the stylesheet so we can better predict what will happen.

Consistency

Another advantage of utility classes is consistency—that is by using utility classes, you reduce the likelihood of multiple similar, but different styles. Enforcing consistency isn’t unique to utility classes though. The same can be achieved with native CSS variables, and even more so with preprocessors like LESS or SASS.

Instead of storing atomic styles in abstract classes like bg-blue-500 font-bold py-6, we can store the same atoms using CSS variables, and apply them in CSS.

background-color: var(--color-blue-500);
font-weight: var(--font-bold);
padding-top: var(--spacing-6);
padding-bottom: var(--spacing-6);

There’s no additional overhead to this method, other than opening a CSS file and applying the rules to the right elements. I’d argue that there’s less overhead to this method, since I can see explicitly that I’m applying padding to the top and bottom, (and I don’t have to learn a new syntax for utility class names).

If you’re using a preprocessor, you can even package groups of rules like this into a mixin, and apply several styling rules to an element in one line. Want an easy way to set something to a standard font? Create a mixin:

// SCSS
@mixin font-regular {
font-family: var(--font-family-regular);
font-size: var(--font-2);
font-weight: var(--font-regular);
color: var(--color-primary);
}
p {
@include font-regular
}

Separation of concerns

While separation of concerns might sometimes be a straw-man, (is a class named stacked-list really purely semantic?), there’s something reassuring about knowing all of your styling rules for an element are in one place, and written explicitly. It’s a personal preference, but I feel like it’s easier to see what’s going on by reading a CSS file than trying to parse a list of class names. CSS isn’t frustrating if you take the time to learn it’s nuances.

The good parts

None of this is to say functional CSS frameworks are bad—use them if they’re your team’s preference. Tailwind’s API is really powerful at defining atomic styles. I can see myself potentially using this framework to define the base styles in a future project.

Another interesting use of utility classed mentioned by Dave Rupert is using them as the “mortar” between components. That is, components themselves are styled using regular CSS, but when combining and aligning components together in a view, it may be easier to reach for a utility class rather than write a selector to add a margin between two components. I’d personally just write the CSS rule, but it’s an interesting use case to consider.

In my view, the advantages gained by using functional CSS frameworks can also be gained by writing clean CSS with variables and mixins, without blurring concerns, without adding a convoluted class syntax, and writing styles as they’re meant to be written—in a stylesheet.

--

--

Adam Thompson

🗽🇨🇦 | Sr. UI Engineer @ MongoDB | Formerly at New Visions for Public Schools | SYDE @ University of Waterloo