https://www.polytomic.com/blog-posts/goodbye-css-modules-hello-tailwindcss
Polytomic logo (white)[609ca3a0e8]
Product
[609ca4dbb1]
OverviewPricing
Use Cases
[609ca4dbb1]
RevOpsData TeamsMarketingSupportAPI endpoints
IntegrationsCustomers
Resources
[609ca4dbb1]
BlogDocumentation
Request DemoLog in
[6180451e68]
Goodbye CSS Modules, Hello TailwindCSS
Engineering
November 2, 2021
Implementing an app redesign is never routine nor easy. Two weeks
after I was hired at Polytomic, I began implementing the app's first
redesign since the founding of the company. The technology choices
and how to get it done were left up to me.
Our frontend codebase is a single-page application powered by Create
React App (CRA), written in TypeScript, and using GraphQL for the
API. The existing styling approach used CSS Modules without a design
system.
CSS Modules are CSS files in which all class and animation names are
scoped locally by default. They get compiled as part of the build
step--with bundler technology like Webpack--and are natively supported
by CRA.
For example, using plain CSS in React:
/* Button.css */
.Button {
padding: 20px;
}
// Button.tsx
import * as React 'react'
// Tell webpack that Button.tsx uses these styles
import './Button.css'
export function Button () {
// You can use them as regular CSS styles
return
Button
}
Button
For example, using CSS Modules in React:
/* Button.module.css */
.error {
background-color: red;
}
/* another-stylesheet.css */
.error {
color: red;
}
// Button.tsx
import * as React from 'react'
// Import css modules stylesheet as styles
import styles from './Button.module.css'
// Import regular stylesheet
import './another-stylesheet.css'
export function Button() {
// reference as a js object
return
}
The advantages of CSS Modules are the elimination of CSS conflicts
across files due to local scoping, explicit dependencies, and the
prevention of global CSS. But, preventing global CSS removes the
expressive power of the "cascading" part of Cascading Style Sheets
(CSS). Additionally, locally scoping CSS class names and IDs do not
prevent duplicate CSS in other files. Defining margin-top: 0;
multiple times in different CSS files will not be mitigated by CSS
Modules.
The biggest drawback to CSS Modules is the lack of design system
utilities. As a frontend team of one, this was a deal breaker. I
needed a styling approach both powerful and expressive with utilities
to create and maintain a design system.
What is a design system?
A design system is a collection of reusable components and styles for
building a cohesive user interface. It abstracts out specific values
in favor of generic tokens or variables.
For example, a color palette will use names communicating intention,
like color-success or color-warning, instead of literal values or
color names, such as green or #FFA500. Updating the value of
color-success becomes a single-line change that propagates to all
instances of that token while retaining the same intention.
Design systems define intention across all aspects of a user
interface, from simple colors to complex components. Constraints on
styling choices ensure available options work together harmoniously.
Constructing a visually imbalanced layout or component becomes more
difficult than with CSS Modules and no design system in place.
Twitter's Bootstrap pioneered the concept of a CSS framework and
component library. It is an excellent choice for pre-made components.
However, Bootstrap's biggest feature--those pre-made components--can be
its biggest flaw.
Bootstrap is a closed system. In past projects, I have rewritten and
redefined large portions of the Bootstrap library to customize style
variables and components the configuration did not allow to be
customized. But, if customization is the default posture, the
efficiencies of Bootstrap are lost. Using CSS Modules would result in
the same amount of work for the same result as a customized
Bootstrap.
With design systems and customization in mind, I narrowed down my
options to Tailwind and Theme UI, which is powered by Emotion, a
CSS-in-JS library. Both libraries offer powerful design system
configuration but--crucially--did not lock me into specific component
architectures. I wanted to avoid the tedious redefinition process
present in pre-made component libraries like Bootstrap.
Comparing Tailwind to Bootstrap, Tailwind is a collection of styling
primitives based on configuration. It is the configuration aspect of
Bootstrap without the pre-made components or lock-in to Bootstrap's
component and configuration architecture.
Tailwind has its own paid component library called Tailwind UI that
is a modern Bootstrap equivalent. It leverages the flexibility of
Tailwind's configuration while providing high quality component
recipes that are trivial to override and customize. I have used
Tailwind UI as a starting point and inspiration for my own
components.
What is Tailwind?
Tailwind is a utility-first CSS framework and build step packed with
classes like flex, pt-4, text-center, and rotate-90 that can be
composed to build any design, directly in markup.
Utility class CSS is an approach to writing CSS that inverts the
definition of style rules. Historically, CSS is written with multiple
style rules attached to a single class, ID, or HTML element.
For example, in plain CSS:
/* button.css */
.button {
background-color: rgb(243, 244, 246);
border-radius: .5rem;
color: rgb(0, 0, 0);
cursor: pointer;
font-family: 'Inter var', 'Helvetica Neue', Arial, sans-serif;
font-size: 1rem;
font-weight: 500;
line-height: 1.5rem;
padding-top: .75rem;
padding-bottom: .75rem;
text-align: center;
}
Button
Utility classes define each style rule as a separate class to compose
individually in the markup instead defining the necessary style rules
under a single class.
For example, in stylesheet.css:
/* stylesheet.css */
.font-sans {
font-family: 'Inter var', 'Helvetica Neue', Arial, sans-serif;
}
.text-base {
font-size: 1rem;
line-height: 1.5rem;
}
.font-medium {
font-weight: 500;
}
.rounded-lg {
border-radius: .5rem;
}
.bg-gray-100 {
background-color: rgb(243, 244, 246);
}
.text-black {
color: rgb(0, 0, 0);
}
.py-3 {
padding-top: .75rem;
padding-bottom: .75rem;
}
.text-center {
text-align: center;
}
.cursor-pointer {
cursor: pointer;
}
Button
Utility class CSS frameworks, such as Tailwind, generate these atomic
classes based on a combination of design system defaults and user
configuration. Every style combination possible from the
configuration and defaults is generated, and, at build time, the
unused classes are removed from the stylesheet.
By staying within the constraints of the design system and Tailwind's
API, using a new value for padding is as easy as putting py-2 into
the markup. Tailwind generated this class in advance from the
defaults and configuration. It also generated py-1 and py-4 but will
remove them from the stylesheet if not added to the markup.
What is CSS-in-JS?
CSS-in-JS is a general philosophy to writing CSS inside of JavaScript
files. All authorship of styling happens within JavaScript files and
in a way native to JavaScript instead of native to CSS, like
Tailwind. This allows CSS-in-JS styling to exploit the full
expressive power of JavaScript.
Theme UI is a CSS-in-JS library for creating themeable user
interfaces based on constraint-based design principles. It is the
CSS-in-JS flavor to Tailwind's plain CSS approach. Both accomplish
the same task--design systems--but differ in their developer
experience.
For example, Tailwind's configuration file to override or extend the
default configuration:
// tailwind.config.js
module.exports = {
theme: {
fontFamily: {
heading: ['Inter', 'system-ui', 'sans-serif'],
body: ['Inter', 'system-ui', 'sans-serif'],
},
colors: {
primary: {
50: '#eef2ff',
100: '#e0e7ff',
200: '#c7d2fe',
300: '#a5b4fc',
400: '#818cf8',
500: '#6366f1',
600: '#4f46e5',
700: '#4338ca',
800: '#3730a3',
900: '#312e81',
},
gray: {
50: '#fafafa',
100: '#f4f4f5',
200: '#e4e4e7',
300: '#d4d4d8',
400: '#a1a1aa',
500: '#71717a',
600: '#52525b',
700: '#3f3f46',
800: '#27272a',
900: '#18181b',
},
},
},
}
// app.tsx
export const App = () => (
)
As both Tailwind and Theme UI help implement design systems, I chose
Tailwind because its CSS output is not locked up inside the
JavaScript runtime. Rather, it compiles into normal CSS classes. The
JavaScript bundle size is smaller because style definitions are not
intertwined with React code. Since Tailwind uses global,
low-specificity CSS classes, stylesheet rule redefinition is
eliminated and file size bloat is avoided.
Implementation strategy
I started my redesign implementation journey with a small, simple
page in the app to convince myself using Tailwind was a viable
strategy. Once I was confident Tailwind would work, I tackled the
remaining pages in decreasing order of complexity. This is a method
of de-risking the project: encounter uncertainty early while
enthusiasm is high.
Feature and maintenance work regularly popped up throughout my
implementation, and I would need to divert from the page I was
redesigning to ship that code. Leaving my long-running redesign
branch to make a feature on master introduced problems with
synchronizing changes.
Every time I would implement a feature on master in the old design, I
would need to rebase my redesign branch and re-implement the work,
which could be simple restyling or serious refactoring. Compounding
the pain of synchronizing new features was the other engineers doing
their work. If I waited a couple days to rebase my redesign branch on
master--as I naively did in the beginning--I would spend up to an hour
working through the conflicts. In hindsight, consistently rebasing
early and often was the right strategy.
Conclusion
The redesign shipped months ago, which gave me plenty of time to live
with my decision to use Tailwind. My conclusion: the choice was an
unambiguously good one. The main reasons are:
* Speed: I can immediately begin writing styles in components
because Tailwind is global and pre-generated from defaults and my
configuration. Styles do not need to be imported or created by
me.
* Shared language: Polytomic's design team uses Tailwind's design
system. When implementing a feature, I can count on design mocks
sticking to Tailwind's values and scales.
* Expressiveness: I have been able to execute every design given to
me. Tailwind has not limited me. When I have used non-Tailwind
CSS, it has been for specific browser overrides, mostly Safari.
* Contextual adjustments: many components need CSS adjustments
depending on their context. Changing a className string rather
than overriding CSS via the cascade is less error prone for me. I
eliminated the problem of specificity collisions.
It's a choice that--in hindsight--I would make again.
Back to all blog posts
[6179da96c2]
Brett Flora
Software Engineer
[60a87031a2][60a8703246]
Want curated data content delivered monthly to your inbox?
Sign-up for our monthly newsletter
[ ][Sign up]
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Similar posts
[60ed60e539]
Company
Minimising Corporate-Speak at the Office
There is too much corporate-speak in offices. Can we do better?
[60d0ce2003]
Company
The Decline of Third-Party Tracking
In the new age of restricted third-party tracking, companies must act
on internal data. Polytomic can help.
[60a866a41b]
Company
Ensuring Data Security: Polytomic Receives its SOC 2 Type II
Certification
Today we are excited to share that we are furthering our commitment
to security by becoming SOC 2 Type II certified.
Product
[609ca4dbb1]
OverviewPricingIntegrations
USE CASES
[609ca4dbb1]
Revenue OperationsData teamsMarketing teamsSupport teams
RESOURCES
[609ca4dbb1]
BlogCase studiesDocumentation
COMPANY
[609ca4dbb1]
About
Get In Touch
[609ca4dbb1]
Contact us
Twitter iconLinkedin icon
Copyright (c) 2021 Polytomic Inc. All rights reserved. | Terms of
Service | Privacy Policy
[609cb10cea]