Now that we’re making the transition from Vue.js to React it’s time to review how we manage styling. Although at first this seems like a fairly minor choice, there’s actually a lot to consider with various techniques available. Before looking at them let me quickly talk about where we are now.
LandInsight - A history of styling
The LandInsight app was first created in early 2014, before Vue.js was officially released. The initial decision made was to style the application using global SCSS files, with some applying common global styling, some specific to individual pages and some for reusable components.
Over time the number of files grew and somehow we reached a point where we had over 20k lines of SCSS. Updating and removing styles could be unpredictable as classes that had only expected to be used in one place had now been used elsewhere and really slowed down development.
By this point we had moved across to Vue.js, so utilising Vue’s scoped styling for components made perfect sense. Styling one component wouldn’t affect anything else in the app and coming up with deeply nested selectors was no longer a worry.
Over the years more engineers joined, the product expanded, we formed teams even developed a couple of new products, LandEnhance and LandFund. As LandInsight’s styling had become a bit of a mess, a centralised design or common set of components was never created, causing the look and feel of our products to become segmented.
The need for a Design System
To unify our products the best method is to create a Design System with a set of patterns for all projects to follow. To increase velocity implementing this system across all our products, we want to develop a component library with a common set of components that can be used and themed across all products.
These components need to be flexible in terms of functionality as each product will have its own requirements, but rigid in design to both save teams time and to prevent introducing inconsistencies.
But before looking too deeply into component libraries, it’s first worth understanding the different styling techniques available.
There are three main options: Classical CSS, Utility-First CSS and CSS-in-JS. A lot of component libraries are built using one of these techniques, or are designed to work best with a particular technique or library. This can heavily influence what the development experience involves when working with them, so let’s look at each in a bit more detail.
This is the traditional approach of storing CSS in separate files to your HTML and JS. Over recent years Classical CSS has become more and more powerful and shouldn’t be immediately discounted.
- Global CSS
- CSS Modules
- CSS and CSS Modules are web standards, making them future proof and blazing fast.
- CSS has expanded over the years, with features like Transitions, Transforms, Flexbox, Grid and variable support. The need for JS workarounds is no longer the case.
- Keeping styling separated from logic helps to keep code concise.
- Preprocessors like SASS and LESS can be used for additional features such as code nesting, flow control and other useful features. It is compiled down to CSS so doesn’t impact performance.
- Global CSS can cause undesired results if used extensively, making scoped styling more complex to implement as the need to override existing styles grows.
- Removing global styling can be a risky task without full visual test coverage
- Without global styling, code duplication and inconsistencies will steadily grow
- Despite becoming more powerful over the years, CSS will never offer the level of functionality JS provides
- Can lead to a lot of duplication/inconsistencies if not paired with global css
- Jumping back and forth between HTML and CSS can be time-consuming
The real problem we’ve had with the classical approach has been how easy it is to add some quick styling, not follow any kind of pattern and dig ourselves into a deeper and deeper hole that it seems impossible to get out of.
For years following a methodology like SMACSS, OOCSS or BEM was a partial fix for this, but now CSS Modules have been introduced it no longer takes a lot of effort to keep your styles in check. There’s a reason why it’s used as the default by popular libraries like Next.js.
Utility classes are CSS classes that serve a select purpose, for example
.bg-white applies the CSS
In a sense they are similar to the global styles that are often created when writing Classical CSS, but the number of
classes cover virtually every common style you would ever want to add to any element.
This means rather than writing your own CSS, you instead apply a series of classes to an element to create the desired effect. This helps to create consistency and can improve development speed when using short, memorable class names.
- Expressive CSS
- Can help enforce consistency and reduce decision-making by using a limited set of variables with fixed names
- Less context switching as stying is built into the HTML
- With practice adding utility classes can achieve results much quicker than when writing CSS as a single class name can apply multiple lines of CSS styling
- Easy to theme as defined classes and variables can be restyled in a central file
- Good performance when using a common set of classes, but only when unused classes are removed with a tool like PurgeCSS
- An entry barrier for new developers to learn class names, both for reading and writing code. IDE plugins can help reduce this, but it still takes some time to get up and running
- You lose the ability to quickly copy/paste online examples, as you need to convert CSS over to utility classes
- Potential for lots of duplication, having to apply the same classes to the same set of components, or very long class sections in HTML making the code hard to follow. Extracting component classes can help prevent these becoming too much of a problem
- No interoperability. As class names are tightly tied to the library used it can be difficult to move away from in the future
- Not everything is achievable with utility classes. In these cases either Classical CSS or CSS-in-JS is needed as a fallback, or designs need to be rethought to avoid these limitations
Another point to note is that styling can become quite rigid when you’re limited to a subset of options, meaning designers need to work closer with developers to ensure designs are achievable without the need for lots of CSS hacks everywhere. Although this can initially feel like a negative, improving alignment between designers and developers and sticking to simpler designs can only be a positive.
Utility-First certainly has a few potential problems and won’t be for every project, but if you want to keep your styling lean, consistent and easy to re-theme in the future the positives can easily outweigh the negatives, and you can save huge amounts of time in the long run.
As with JSX quickly gaining popularity for dynamically generating HTML in JS rather than the traditional approach of manipulating static templates, CSS-in-JS follows the same principle. Styling can change significantly based on an applications state. Using JS to dynamically apply the correct styling is much simpler and cleaner than using multiple conditional class names or HTML blocks.
The other core concept is that as you define your CSS within your JS component, that CSS is scoped to that component and won’t effect the rest of your code. This means you can easily update the styling within one component without effecting anything else.
- Styled Components
- Styled System
- Styled JSX
- Provides an API to describe state-based styles making it very flexible, great for working with complex situations
- Great for building reusable components, and easy to extend base classes
- Components are easily theme-able via central theme files
- Most libraries support Classical CSS, so are fairly simple to pickup and start working with
- If you’re not careful logic can become more and more complex. Not only does this make code more complex to understand and testing harder, it can also significantly impact performance
- Additional complexity when choosing the CSS rendering strategy needed, see more here
- Logic can end up within CSS templates, which can be difficult to test
- Different approaches can be taken to achieve the same result, reducing consistency
- Naming components is hard and things can quickly become messy without some form of standardisation
- It’s possible to introduce security vulnerabilities if you don’t escape use inputs
- No interoperability. Although some libraries use close to Classical CSS, each library has its own implementation, so it can be difficult to swap libraries later down the line
CSS-in-JS is easily the most powerful solution and most likely the way forward if you have any complex situations you need to support. It can also make a lot of sense if you’re already using JSX, as it means you can now keep everything within your components. However, with additional power comes added complexity and there’s more to consider when it comes to performance and code complexity.
Looking at these options there is no obvious winner. Each have their own advantages and disadvantages, with different libraries trying to make improvements in different ways with varying popularity.
CSS Frameworks Usage - https://2020.stateofcss.com/en-US/technologies/css-frameworks/
For Classical CSS, SASS has been the most popular approach to take for years, largely due to its support for nesting selectors. However, as CSS gains support for more and more features preprocessors are becoming less popular.
Tailwind is currently taking the Utility-First world by storm, having massively surpassed its competitors and continuing to grow and improve. This is largely down to getting the design tokens used at the core of the system spot on, allowing developers to achieve great looking designs at pace.
When it comes to CSS-in-JS things are a bit more complicated. Styled Components is the most established library, but there are several alternatives quickly growing in popularity, so it’s not immediately obvious which the ‘right’ one to go with is. Some libraries are even combining Classical (Styled JSX) and Utility-First (tailwind.macro) features.
CSS-in-JS Usage - https://2020.stateofcss.com/en-US/technologies/css-in-js/
For now, it’s good to keep our options open and take a look at some existing component libraries in the next post to get some inspiration from and potentially build our own one off of.
We are the engineers behind LandInsight and LandEnhance. We’re helping property professionals build more houses, one line of code at a time. We're a remote-first company with our tech team in Europe, and yes, we're hiring!