Consistency and drudgery in UI design
Designers have long been suffering the absence of a true layout engine in their design tools. This makes day-to-day design tedious, just like converting those designs to code is tedious for front-end programmers.
The key to good UI design is visual consistency. It is also the key to good HTML & CSS.
But in practice, websites are inconsistent even in their brand colors. Their spacings will be all over the place; everyone rolls their own buttons; and text sizes will vary widely. Inspect a little, and most of these interfaces reveal no rhyme or reason.
This absence of a coherent visual language is the foremost source of friction between designers and developers.
But it manifests in surprisingly subtle ways. Consider this design:
It looks pretty consistent, doesn’t it? But see its actual measurements:
The spacings between the boxes are off by a pixel, and the colors, while perceptibly the same, are also different from each other. If as a developer I have to construct a reusable component for the box, which values should I pick?
People are fine, tools are lazy
This kind of inconsistency is very common in real-world design files. But that is not because designers are lazy. The vector design tools they use are free-form and offer little affordance in keeping designs normalized. So every change, however minor, screws up things, and the designer has to manually fix them to maintain consistency. Here’s an example:
On the browser, as an element grows in height, everything below will automatically move down to make space. But this doesn’t happen in free-form UI design tools like Sketch, Figma, and Adobe XD. Instead, the designer has to enlarge the box, move the text, and ensure that the original spacings are maintained. That is very tedious and lapses are bound to happen when you do this hundreds of times a day.
So it is no wonder that design files are imperfect, and it is going to remain the case until vector design tools start shipping with a layout engine. But these inconsistencies puts the developer in a bad place when they try to code up the design. For every inconsistency, they have to either:
- Pick the values exactly as they are in the design, consistency be damned. Or,
- Arbitrarily decide canonical values so the front-end code remains clean
Neither one is satisfactory. In the first case I’m degrading my codebase with magic values, and that is a trap:
Front-End Developers tend to assume that there is some very important reason each design is exactly as it is. They assume that the difficult-to-implement clock dial has to look exactly like that initial design because they view all the UI Designer’s work as that of an expert. They therefore don’t try to simplify or discuss. They just assume there is some clever reasoning behind everything & that the UI Designer will create the perfect solution on the first attempt
But despite that if I still want to faithfully reproduce the design, then my CSS will look like this:
This is terrible code. Next time someone comes along and has to add another box, which one of the two will they duplicate? They’d be wondering what divine purpose underlie the 1px difference in margins, and cursing whoever wrote it in the first place.
This sort of blind, pixel-perfect translation of designs into code is how most CSS codebases start to become incomprehensible balls of mud that programmers fear to touch.
But if the developer normalized the values themselves, the code will become cleaner:
However, this isn’t as easy as it looks — it is often ambiguous as to which value should become canonical. Picking the wrong thing can mess up important design details and lead to painful back-and-forths with the designer.
This problem of inconsistency in designs and the difficulty faced by developers in disambiguating them, remains the biggest source of friction in front-end teams. If we can get rid of it, we’ll eliminate the need for constant redlining and rework, improving morale, and increasing productivity.
Large teams manage but small teams struggle
Large organizations solve it with design systems. Designers can assemble new pages from a library of components rather than creating everything from scratch every time. And developers can build those designs equally fast since the components are already built and can be put in production immediately.
Since these components are reused across the organization by dozens of people, each component justifiably receives large ongoing investment. This allows for great care and precision in their UI design and they are thus protected from mistakes and inconsistencies.
But small teams cannot afford full-fledged design systems. They often start building component libraries and invest months of effort, only to realize much later the true ongoing cost of maintaining it.
There is however a solution — a light-weight approach to both design and designer-developer collaboration that can eliminate these problems completely and is a breeze to follow for even solo practitioners. I think of it as the explicit design scale approach to building user interfaces.
The explicit design scale approach to building user interfaces
A design scale enumerates every possible visual style that can be allowed in a project. It includes visual elements like typography, colors, spacings, and borders. It must be both human-readable and machine-readable.
Here’s an example:
"roboto": "Roboto, sans-serif"
When following the explicit design scale approach, the designer must define a scale upfront and adhere to it in the design. Similarly, the developer must explicitly encode it in their codebase and ensure that any CSS style in the code can only be defined based on the values in the scale.
But wouldn’t this constrain the design unnecessarily? Adam Wathan and Steve Schoger addresses it in the most excellent Refactoring UI:
You shouldn’t be nitpicking between 120px and 125px when trying to decide on the perfect size for an element in your UI.
Painfully trialing arbitrary values one pixel at a time will drastically slow you down at best, and create ugly, inconsistent designs at worst.
Instead, limit yourself to a constrained set of values, defined in advance.
Or as they say in Zen:
In the beginner’s mind there are many possibilities, but in the expert’s there are few.
— Shunryu Suzuki
The Zen master wants us to be open to many possibilities by always keeping a beginner’s mind. But in design, great interfaces are created only by actively limiting possibilities.
Explicit scales make everyone happy
Imagine that you’re trying to write HTML & CSS for the following design and you encounter the 39px margin between the two elements:
In the absence of a scale, you’ll have to either pollute your code with the magic value of 39px, or you will have to talk to your designers and clarify whether they need that exact same value there.
But if there is a scale that the designer and developer has already agreed to, then we can just adopt one of those values. Given the below scale, we’ll snap the 39px margin to the value closest to it, which is 32px.
This frees up the designer as well — since they know that the developer can tolerate ambiguities easily and correctly, they can focus on the actual design work rather than having to worry about minor issues that need constant manual cleanup.
Adopting explicit design scales in your next project with Tailwind CSS
Design scales don’t need to be sold to designers; it is already a part of their mental model. If not made into an explicit styleguide, it exists at least implicitly — ask them “what is our usual padding for buttons?”, and they’ll have a pat answer. “what about larger buttons?” and they’ll tell you the next value in their spacing scale.
Front-end programmers however are sorely lacking in this understanding and it shows in how almost every CSS codebase in the world transforms into a spaghetti monster over time. It also makes for very fraught relationship with designers — we’re people who’ve to come together to create something new, yet we’re unable to understand each other’s language. It is a mystery that anything gets done at all.
The fastest way for programmers to bridge this gap is to use a front-end programming environment that makes design scales explicit and prevents them from using magic values that are not part of the scale. This is best done by Tailwind CSS, the most powerful functional CSS library available today.
In Tailwind, the source of truth is
tailwind.config.js, which looks like this:
"roboto": "Roboto, sans-serif"
Tailwind generates all the CSS classes we need from this scale. Here’s an example markup that uses these generated CSS classes, based on the above scale:
<div class='text-16 text-blue0 roboto m-16'>Hello world</div>
It means this:
/* text-16 */
/* text-blue0 */
/* roboto */
font-family: Roboto, sans-serif
/* m-16 */
When using Tailwind, the programmer rarely has to write their own CSS classes or do any ad-hoc styling. The CSS classes are generated from the design scales, and we just have to reuse them, and that makes it impossible for us to go wrong and deviate from the scale.
This approach of using only single-purpose CSS classes has many names — Functional CSS, Utility CSS, Immutable CSS, and Atomic CSS. It is an emerging pattern which runs contrary to received wisdom in front-end programming. But it is a sound abstraction that grows on you and is fast to build, understand, and maintain.
Tailwind CSS in particular, with its ability to generate CSS classes based on custom design scales, lends itself extremely well to projects that have custom-built UIs made in vector drawing tools like Sketch or Figma.
Tailwind has extensive documentation, great stewards, and an active and passionate community. Learn more about Tailwind from its home page.
(What is a Medium post without a shameless plug?)
This post came from our experience building Protoship Codegen, a tool that helps you build SaaS web applications, e-commerce portals, and content-rich websites faster by converting your Sketch designs into the perfect HTML & CSS.
We saw hundreds of real-world designs during this time and saw that they were replete with inconsistencies.
Translating them faithfully would’ve lead Codegen to generate clumsy code. So we adopted design scales as a core abstraction in the software, and included a feature we call Snap to Scale which snaps all values in the designs into a user-defined scale.
Codegen was possible thanks to explicit design scales using Tailwind, Sketch’s plugin architecture, and the amazing improvements in CSS itself. Take a look and see for yourself:
Become an early-adopter here! https://protoship.io/join/
And if you have thoughts and stories to share about design tooling, consistency, and drudgery, please tell us here in the comments below.