How TailwindCSS helps you implement a design system (and why it can be better than CSS-in-JS solutions)
As a programmer who has held design and user experience roles, I appreciate the process and value those disciplines add to a product and the companies that make them a priority. I've worked at organisations that treat design and UX as an after-thought. Functionality trumps all. While this approach gets you so far (you may release a feature into the market before anyone else for example), there comes a point where you lose customers because a competitor simply makes it so much easier to do the same thing you do - through great design.
Throughout my career, I have noticed that there is often a gap in collaboration and communication between Design and Engineering - even at companies that value both. This gap naturally leads to issues in efficiency, consistency and a lack of understanding around each role's goals and challenges.
Common pain points between Engineering and Design teams
- Designs are created with no/or very little engineering input so they can be difficult to implement or fail to take into account technical limitations.
- Designs follow a loose pattern of consistency, slight changes for a one-off use case etc. Since they often need to be implemented 'pixel-perfect', this leads to a large amount of custom CSS which is not re-usable. Variables only get you so far.
- There is no single source of truth to see what already is in use within the application from both design/engineering perspectives.
- CSS is often overly specific, static, and has a variety of approaches scattered throughout the project (some BEM, some utilities, global html tag styling, presentational vs content classes). This is indicative of a failure to plan an approach that scales, both for design/use-cases and with onboarding new developers.
- There is often no cross-team design system, and without one, creating bespoke CSS is very easy. As a consequence, CSS file sizes get larger with every change/addition causing your users to wait longer to load your app and forcing developers maintain more CSS.
- Working in React projects, teams don't always capitalize on React's composable nature as much as they should. This is also hard though because of the lack of consistency.
All of these things make working as a developer less fun and I encountered them all when I started working on a React application a couple of years ago. It was particularly tiresome creating so much custom CSS, working in a maze of it and not having any documentation around the approach or what was currently built. It was also frustrating getting inconsistent designs that made it hard to re-use anything I had previously created.
If I change this line or remove this property, what am I going to break? Any developer who's worked with CSS at scale
I really wanted to find a way to make my life easier and improve the collaboration between the design and engineering teams; speak the same language to deliver the product we all had a hand in making. What we needed was a design system. Not just a sketch file that the design team used. But an approach to unify how designers created their designs and how engineers implemented them.
What is a design system?
For me a design system is the way you produce a product and get it into the hands of your customers. It's all encompassing. When most people explain what a design system is though, at least in the software industry, it usually goes something like this:
Design systems are a collection of rules, constraints, and principles implemented in design and code. Sylvee L
Because my main pain point with this project was dealing with the CSS, I started to look at ways to make it suck less.
The goals:
- Consistency and structure
- Reduce development time
- Maintainabilty
- Scalabilty
- Performance
CSS-in-JS solutions
Styled components was starting to get talked about more as devs expressed their issues over "running out of names to call things"; "writing basically the same CSS but it needs to be just a bit different"; "the CSS is so specific, I have to write even more specific code to apply my change"; "what can I re-use if anything?"
Benefits
- Namespacing/scoping
- Tree-shaking
- Composability
Drawbacks
- You are locked into an implementation. What if you wanted to use your styles in another project altogether that wasn't using styled-components or React/Vue altogether?
- Obfuscated source class names. You can't look at the source and know what's going on. You need to rely on your dev tooling to pick up what affects what via inheritance.
- There is often an unavoidable overhead of using these approaches - either through build time, or run time.
- You are still writing CSS, therefore the larger your project gets, so does the CSS.
- There's a learning curve to all these solutions.
Utility CSS as a solution
It was about this time that I started looking at utility CSS with more discerning eyes. I had heard of this approach before but like any developer who first sees something like this:
<p class="f6 ttu b fl w-100">
<strong class="red bg-white">Hi! I'm styled with utility classes!</strong>
</p>
There was a strong reaction of the negative kind.
This is just inline styling! Those class names are nasty! Most developers (minus or plus additional sass - pun intended)
It's not inline styling by the way. You don't have the specificity or the problem of re-use you have with inline styling.
I came across a talk by Simon Swiss and then articles by Adam Wathan and Sarah Dayan that all articulated my experience with CSS at scale and found utility CSS to be not only faster to use but a natural evolution to working with CSS in large projects. I highly recommend checking them out; they have done a great job explaining the arguments around the topic.
With most utility CSS frameworks, you define what your project will use such as measure units, colours and shadows for example, and it will generate individual CSS classes for everything you need to build your UI. Design teams can then add the class names to their tools and designs which is crucial for consistency across teams.
Apart from those things, why consider a utility CSS approach and how might it be better than implementing a CSS-in-JS solution?
Utility classes expose a well-defined API that you can use to compose more complex components. You’re not re-writing styles; instead, you’re relying on classes that define styles and behaviours once and for all. Sarah Dayan
Benefits
- Refactoring CSS is hard and time consuming. The real way to scale CSS, is to stop writing CSS. Utility classes allow you to do this more than any other approach.
- All styles are defined and maintained separately. This allows code reuse, usage of pre-processors and browser caching.
- Provides highly composable, low-level utility classes that make it easy to build complex user interfaces.
- Consistency, portability and performance. These things may be the main advantage over styled-components and those of that ilk. It's easier to 'catch' custom CSS in pull requests and question their need.
- Used in combination with PurgeCSS (which rids your project of unused CSS) gets us the tree-shaking benefit that many CSS-in-JS solutions boast.
Drawbacks
- Class names can become long. This is really a cosmetic issue and once it's tucked up in a component, you don't really need to work with it all the time.
- There's a small learning curve around remembering utility nam and how to approach writing more responsive CSS.
Why TailwindCSS?
- The class names are semantic by default so are easy to remember.
- Documentation is first-class.
- It has no opinion of how your site should look but it is highly configurable. It should be noted though, that the defaults for the library have been thoughtfully selected in collaboration from designer, Steve Schoger. This, in my opinion, gives Tailwind a nice advantage over other projects and highlights the benefits of designer/engineer teamwork.
- Responsive out of the box.
- Written in PostCSS (a tool for transforming CSS with JavaScript) and configured in JavaScript so is ready to be integrated in most SPA projects. There is a CDN version plus CLI tool that will just generate the utilities if you prefer.
- Utility-first, not utility-only. When repeating patterns occur, and it’s likely to be used in other components, that’s when you create a custom class.
Selling it
All of this discovery and thinking lead me to present a spike (a quick protoype of how something could work) using TailwindCSS within our project and explaining the benefits of utility-first CSS. I was able to delete close to 600 lines of custom CSS refactoring one component pattern!
In combination with being able to provide a common language to use between the design and engineering teams, it was full steam ahead using TailwindCSS as the foundation of our design system. Storybook was set up to document our React components that were now built with a utility-first approach. Designers updated their Sketch files to be in sync with what developers were using to implement the designs. We were achieving the goals I had set out to reach.
Each project has to take into account it's own requirements, but the next time you find yourself lost in a spaghetti bowl of CSS, perhaps try out utility CSS as a starter.