One of the current major concerns of frontend developers is finding a fair balance between performance and the development velocity of their team. When it comes to CSS, teams benefit from the local scoping of style rules, shareable utilities in a powerful language, and testing to identify regressions.
And although performance with the runtime generation of CSS is not ideal, there are other approaches that address the issue like inlining critical CSS. Alternatively, CSS-in-JS is gaining momentum - and according to The State of JS' annual survey for 2020, sixty percent of developers are using Styled Components, a popular CSS-in-JS library, in a current project.
There are a lot of reasons to love CSS-in-JS approaches to styling your app or website, and a lot of lingering doubts as to whether it's the right choice for many teams. But the real question here is: how does performance vary between CSS versus CSS-in-JS? In this article, we'll briefly cover what the choices for styling a modern frontend framework are, what are some of the advantages and disadvantages for each of them, and what the data show to help quantify an approach that makes sense for you.
Styling a Modern Web Frontend
Frontend frameworks are the anchor of agile apps and websites. Following design patterns like Jamstack and MACH, frontends now are often implemented in React, Angular, Vue, and similar platforms, or in further abstractions such as static site generators like Next and Gatsby. There are a number of common strategies for using static CSS for styling in client-side frameworks. There are also dynamic CSS strategies that leverage strengths of the various frontend platforms with some performance penalty. We'll start with looking at static approaches to styling.
Vanilla CSS Styling Strategies
You can use global style sheets to style a framework-based frontend, just as you can with full-stack frameworks like Rails and Laravel. Using preprocessors like SASS/SCSS and naming conventions like BEM can help with organizing the work of a team, but there are a lot of challenges to overcome.
Frontend frameworks allow setting CSS classes on elements and often use module bundlers like Webpack to extract CSS imports.
Twitter's Bootstrap framework introduced a lot of front-end developers to a utility-first approach to styling. This technique provides low-level CSS classes named according to their intended purpose to apply to elements, like
bg-white to add a background color of white.
Tailwind CSS is a utility-first framework that's become wildly popular with frontend developers, ranking first in interest and usage on CSS 2020's annual dev survey. There's lots of good material available for using Tailwind with all of the popular frontend frameworks.
Frontend frameworks provide a mechanism to apply inline styles to elements. The styling is output to
style attributes on elements.
Native CSS now supports custom variables, which is a really great leap forward because CSS variables can be manipulated from frontend client code and CSS has a rich set of functions to use with them. Using CSS variables enables programming patterns for styles that are more like a regular programming language than a static declarative language.
Diving into CSS Modules
All of the static CSS styling approaches we've talked about so far are usually implemented with a layer of global styling, and depend on CSS inheritance which comes with special challenges for project maintenance.
CSS Modules sit between static CSS techniques and CSS-in-JS strategies for styling frontend framework output.
They offer local scoping of styles, but styling is maintained in plain CSS files kept alongside the component. With TypeScript, you’ll get a compile error if you use a class that doesn’t exist in your style sheet.
What are some CSS-in-JS Styling Strategies?
CSS-in-JS libraries provide completely dynamic CSS using natives available in frontend frameworks like component props and state. There's been a lot of collaboration between the major CSS-in-JS libraries, and there's been a lot of work put into standardizing on common APIs.
Styled Components and Emotion are two of the most popular libraries, and there are other useful and compatible libraries in the ecosystem like Styled System to implement a design system, and Rebass and Theme UI for global theming and a component library. Both popular libraries are framework agnostic, and there are also framework-specific approaches like Radium and Styled JSX. They work by generating a random classname to apply to the element being styled, and inject an HTML
<style> element into the document's head for the style.
When the application state changes and components are updated, they are also recalculated. This makes "light" and "dark" theme switchers a piece of cake.
Comparing CSS-in-JS and CSS Setups
The usual knock against CSS-in-JS libraries involves performance.
These libraries do some relatively heavy lifting under the hood: compiling and interpolating props for styling directives in tagged template literals, building a graph of managed elements, preprocessing CSS, and injecting the CSS into the page.
It's useful for us to consider the advantages of CSS-in-JS approaches that can make this trade-off worthwhile, and in the next section we'll quantify just what the performance penalty is against vanilla CSS approaches.
Why Should You Use a CSS-in-JS Library?
A good use case for CSS-in-JS is a stylish client portal that can be customized visually by users or white labeled using an easy and intuitive GUI control panel. Styled System can use either Emotion or Styled Components underneath and maps props to a design system. Design props can be managed in persistent storage as per-user config.
CSS-in-JS libraries are great for teams.
They largely eliminate the specificity hierarchies that determine the final styling of an element and play such a large role in vanilla CSS techniques.
CSS-in-JS also makes extracting stylings for just the content shown in the client's initial viewport (critical CSS) easy. Including these stylings in the document's head can make a huge difference in a user's perceived performance, although it's possible to set up critical CSS extraction for vanilla styling setups as a CI step.
Some Reasons to Use Vanilla CSS
While loading large external style sheets can negatively impact performance metrics, simple CSS is faster for browsers to render than styling that is generated using code.
Static CSS has a gentler learning curve than CSS-in-JS libraries with less complexity. Especially nice for simple projects, there’s no requirement for setting up a build system with vanilla CSS. There’s a huge landscape of learning resources, from Pluralsight courses to Youtube videos.
Animation is still a weak point of styling-with-code approaches. Standard CSS can achieve much of what CSS-in-JS libraries offer. W3C’s CSS specification now includes variables and easier layout with primitives like Flex and Grid. With the right tooling like CSS modules, styles can be locally scoped.
Vanilla CSS certainly makes ramping up a new project easy.
There's less complexity, less overhead, and less tooling to put to use. Using well thought out styling libraries like utility-first Tailwind CSS can help ease the strain of using plain CSS in a team setting. But for providing dynamic digital experiences, CSS-in-JS libraries provide an out-of-the-box experience with sample sites for most frontend frameworks.
They scale well as the number of people contributing to an app or website grows and maintenance needs arise.
Code approaches to styling have some performance penalty associated with them. Whether the advantages outweigh the disadvantages of any styling approach for modern frontend frameworks depends heavily on the nature of your project, capabilities, and scale.
Thinking of improving your site or app's styling workflow? We can help in that regard.
Curious to see how? Check out our Avenues case study to see how we implemented strict client design guidelines with Styled Components.
Or better yet, schedule a talk with us!
CLICK HERE to schedule 1-on-1 talk and learn more about what we can do for you and your business.