I have to admit: I don’t always have time to keep up with all the new CSS technologies, and I don’t always know which ones are worth my time.
To organize my thoughts, I produced an overview of all the major CSS technologies that are gaining popularity today and might still be relevant tomorrow: CSS-in-JS, Component-Oriented CSS*, and Utility First CSS.
I wrote about the big ideas behind them and the consequences of these ideas for code architecture, performance, and dev velocity.
You can try all the code examples via Codux by clicking here.
- Component Oriented CSS: A term I coined, it refers to CSS frameworks, mostly preprocessors, that scope CSS locally, such as Stylable, or CSS Modules.
CSS (Cascading Style Sheets) was initially released in 1996 during the Browser wars: Microsoft's Internet Explorer and Netscape Navigator. Each tried to define the web, which, at the time, was mostly static pages that displayed data.
One of the great advantages of CSS was the way styles “cascaded” from a parent to affect all of its children. This worked very well for the relatively small projects that existed in 1996 because it reduced duplication, made CSS easy to understand, and the inheritance easy to trace.
However, as projects grow in complexity, cascading becomes a liability; CSS rules often depend on the HTML structure, so every change in either requires changes in both. In addition, a small change in any rule can have unintended consequences on other pages due to global scope.
Styled Components is a package that is installed using the package manager.
Button component is created by providing the HTML tag (button), and the style is provided as a string using a tagged template string. Inside, Styled-Components creates a unique className for the new Button Component and adds the
className to the CSSOM*
styled.button returns a new component that uses the newly created style so that it can be used.
- CSSOM (CSS Object Model) is like the DOM, but for CSS, it allows adding CSS at runtime.
Emotion is also a package that can be installed using the package manager. It works similarly to Styled-Components: the CSS prop is replaced with a uniquely generated
className, and the style is added to the CSSOM.
By now, most libraries that allow CSS-in-js support both syntaxes.
The tagged-template-string format is useful because we can directly load legacy CSS from files, but it doesn’t support any syntax highlighting and code completion supported by the CSS prop out of the box.
- Local scoping, since a unique className is generated for each component, the styling is now scoped to the component, and styles will no longer override each other simply because they are no longer global. In other words: these components can be extended but not modified from the outside.
- The main building block of our app is now the component. This no longer feels like regular CSS. Instead, it becomes functional programming: the styling of a component is now an integral part of it, and they are both encapsulated into a single unit that exposes an API that can be reused.
- Loss of separation of concerns, CSS was designed so that content would be separated from presentation, with CSS-in-js component logic, animations, themes, and layouts all mixed together in the component; when not carefully managed, this could lead to unmaintainable code.
- Browsers can render regular CSS much faster than styling that requires code to run; this can be alleviated by separating the critical CSS needed to load the user’s initial viewport and is further helped by server-side rendering.
- An Extra layer of complexity needs to be set up and maintained and requires a steeper learning curve than vanilla CSS. This may be worth it for teams working on relatively large projects but can be detrimental in smaller ones.
This is a newer solution, which I have yet to try out extensively, but, after reading this article, a friend recommended that I also mention Vanilla Extract.
Vanilla Extract "uses TypeScript as a preprocessor" so it looks like CSS, but it's written in TypeScript, and so it requires learning a new Syntax.
It offers some of the runtime advantages CSS-in-JS has through css variables and classes, but unlike CSS-in-JS it's very fast because it generates atomic static CSS in build-time.
Vanilla Extract is still very new, so it doesn't enjoy the popularity some of the other frameworks do, but it is definitely on my watch list.
keep everything we liked about CSS, and build upon the good work that the styles-in-JS community was producing. So, while we’re bullish about our approach and firmly defend the virtues of CSS, we owe a debt of gratitude to those folks pushing the boundaries in the other direction. Thanks, friends! 👬👫👭 — css modules team
CSS Modules is a pre-processing step: by default, styles are scoped locally to the current component, and the transpiler ensures no conflicts.
Let’s see how it works. First, we add a
button.css, just like with good old CSS.
Next, we import styles from the CSS we just created and apply
classNames from it to our JSX:
The transpiler will make these styles locally scoped, and the result looks something like this:
By scoping locally, we sidestep the global scope issue but lose some of the advantages gained by cascading. Instead, to avoid code duplication, we use the composes keyword:
Let’s say we want a cancel button that is the same but with a different text color:
CSS modules adds both
classNamesto the component.
CSS Modules helps manage large CSS projects by adding namespaces. Stylable is a CSS preprocessor that takes it even further by adding a type system.
Stylable exposes an API that can be styled, meaning selector issues are visible in build-time rather than run-time. For example, you’ll see it in the IDE if a button no longer supports an icon. Every developer knows that detecting and fixing a bug costs exponentially more during acceptance testing than during coding.
Stylable files look very similar to CSS files, but they are scoped:
In addition, because we now have an elaborate type system, we can now extend this button from parent components; for example, this could be a dialog containing our button:
Try it out with Codux
- We can “compose” some of the CSS-in-JS advantages here: local scoping, composition, and the main building block of our project is still the component. That’s why I called it “component oriented.”
- Concerns are separated, just like with CSS: themes and layouts are, by default, separated from the component without the need to manage them carefully.
- Runtime performance is not affected, unlike CSS-in-JS, which executes in runtime and potentially slows down your app; this is a preprocessing step, resulting in plain CSS, which the browser is already heavily optimized for.
- No longer dynamic, complex logic is more challenging to implement; some styling features can’t be implemented without JS.
- An extra layer of complexity, though with a gentler learning curve than what CSS-in-JS offers.
The idea is to have reusable, tiny classes like
rotate-90 that can be composed and used in any component.
Consequentially, around 90%-95% of most projects’ CSS can be translated into these atoms. For example, the component below has the following HTML code:
- HTML is now dependent on CSS; it has to know that certain CSS classes exist and how to combine them properly, making the CSS completely independent of the HTML and allowing programmers to quickly iterate on the design system regardless of the content. Smooth Iterations mean fast improvements and sleek products; that’s why Utility-First CSS results in some of the best component libraries. Check out Tailwind, and Bootstrap
- The design system is more consistent,
border-dark-soft, will always have the same color and width, and can be easily changed in all places. However, this can easily be achieved using composition and variables.
- CSS files are much smaller. CSS usually grows linearly with project size: more components mean more classes and larger files, while utility-first CSS grows logarithmically with project size:
border-dark-softis defined once and used many times
- CSS was designed to be independent of HTML, that’s why we name classes based on content: button, author-bio, etc., or in other words, CSS is dependent on HTML and needs to know what classes were used. At the end of the day, the more volatile parts of our software (that change the most) should depend on the ones that change the least: in most projects, users like features and functionality (e.g., components) to be continually added, but they like the design-system to stay the same.
- The naming convention requires a learning curve: what is
py-2? We end up with these long unreadable classes:
py-2 px-4 border-r border-dark-soft, that are hard to learn and difficult to understand.
- Adds another layer of complexity to our stack, sometimes requiring setting up the naming conventions and adding a post-CSS processor to minify the result.
CSS has a fascinating future ahead of it. These technologies are here to stay, grow, and become easier to use. However, teams must be careful when selecting a specific technology over others. In general:
- CSS-in-JS is worth the performance hit when complex logic is associated with styling.
- Component Oriented CSS is perfect for organizing large projects, with many independent components.
- Utility-First CSS seems to produce the best-looking interfaces with the least amount of effort, this makes a lot of sense: if the CSS is decoupled from the component structure, it can be iterated upon frequently.