Why We're Breaking Up with CSS-in-JS
Sam Magura

Sam Magura @srmagura

About: 204 No Content

Location:
Raleigh, North Carolina, USA
Joined:
Sep 21, 2021

Why We're Breaking Up with CSS-in-JS

Publish Date: Oct 16 '22
640 146

Hi, I'm Sam — software engineer at Spot and the 2nd most active maintainer of Emotion, a widely-popular CSS-in-JS library for React. This post will delve into what originally attracted me to CSS-in-JS, and why I (along with the rest of the Spot team) have decided to shift away from it.

We'll start with an overview of CSS-in-JS and give an overview of its pros & cons. Then, we'll do a deep dive into the performance issues that CSS-in-JS caused at Spot and how you can avoid them.

What is CSS-in-JS?

As the name suggests, CSS-in-JS allows you to style your React components by writing CSS directly in your JavaScript or TypeScript code:



// @emotion/react (css prop), with object styles
function ErrorMessage({ children }) {
  return (
    <div
      css={{
        color: 'red',
        fontWeight: 'bold',
      }}
    >
      {children}
    </div>
  );
}

// styled-components or @emotion/styled, with string styles
const ErrorMessage = styled.div`
  color: red;
  font-weight: bold;
`;


Enter fullscreen mode Exit fullscreen mode

styled-components and Emotion are the most popular CSS-in-JS libraries in the React community. While I have only used Emotion, I believe virtually all points in this article apply to styled-components as well.

This article focuses on runtime CSS-in-JS, a category which includes both styled-components and Emotion. Runtime CSS-in-JS simply means that the library interprets and applies your styles when the application runs. We'll briefly discuss compile-time CSS-in-JS at the end of the article.

The Good, The Bad, and the Ugly of CSS-in-JS

Before we get into the nitty-gritty of specific CSS-in-JS coding patterns and their implications for performance, let's start with a high-level overview of why you might choose to adopt the technology, and why you might not.

The Good

1. Locally-scoped styles. When writing plain CSS, it's very easy to accidentally apply styles more widely than you intended. For example, imagine you're making a list view where each row should have some padding and a border. You'd likely write CSS like this:



   .row {
     padding: 0.5rem;
     border: 1px solid #ddd;
   }


Enter fullscreen mode Exit fullscreen mode

Several months later when you've completely forgotten about the list view, you create another component that has rows. Naturally, you set className="row" on these elements. Now the new component's rows have an unsightly border and you have no idea why! While this type of problem can be solved by using longer class names or more specific selectors, it's still on you as the developer to ensure there are no class name conflicts.

CSS-in-JS completely solves this problem by making styles locally-scoped by default. If you were to write your list view row as



<div css={{ padding: '0.5rem', border: '1px solid #ddd' }}>...</div>


Enter fullscreen mode Exit fullscreen mode

there is no way the padding and border can accidentally get applied to unrelated elements.

Note: CSS Modules also provide locally-scoped styles.

2. Colocation. If using plain CSS, you might put all of your .css files in a src/styles directory, while all of your React components live in src/components. As the size of the application grows, it quickly becomes difficult to tell which styles are used by each component. Often times, you will end up with dead code in your CSS because there's no easy way to tell that the styles aren't being used.

A better approach for organizing your code is to include everything related to a single component in same place. This practice, called colocation, has been covered in an excellent blog post by Kent C. Dodds.

The problem is that it's hard to implement colocation when using plain CSS, since CSS and JavaScript have to go in separate files, and your styles will apply globally regardless of where the .css file is located. On the other hand, if you're using CSS-in-JS, you can write your styles directly inside the React component that uses them! If done correctly, this greatly improves the maintainability of your application.

Note: CSS Modules also allow you to colocate styles with components, though not in the same file.

3. You can use JavaScript variables in styles. CSS-in-JS enables you to reference JavaScript variables in your style rules, e.g.:



// colors.ts
export const colors = {
  primary: '#0d6efd',
  border: '#ddd',
  /* ... */
};

// MyComponent.tsx
function MyComponent({ fontSize }) {
  return (
    <p
      css={{
        color: colors.primary,
        fontSize,
        border: `1px solid ${colors.border}`,
      }}
    >
      ...
    </p>
  );
}


Enter fullscreen mode Exit fullscreen mode

As this example shows, you can use both JavaScript constants (e.g. colors) and React props / state (e.g. fontSize) in CSS-in-JS styles. The ability to use JavaScript constants in styles reduces duplication in some cases, since the same constant does not have to be defined as both a CSS variable and a JavaScript constant. The ability to use props & state allows you to create components with highly-customizable styles, without using inline styles. (Inline styles are not ideal for performance when the same styles are applied to many elements.)

The Neutral

1. It's the hot new technology. Many web developers, myself included, are quick to adopt the hottest new trends in the JavaScript community. Part of this is rationale, since in many cases, new libraries and frameworks have proven to be massive improvements over their predecessors (just think about how much React enhances productivity over earlier libraries like jQuery). On the other hand, the other part of our obsession with shiny new tools is just that — an obsession. We're afraid of missing out on the next big thing, and we might overlook real drawbacks when deciding to adopt a new library or framework. I think this has certainly been a factor in the widespread adoption of CSS-in-JS — at least it was for me.

The Bad

1. CSS-in-JS adds runtime overhead. When your components render, the CSS-in-JS library must "serialize" your styles into plain CSS that can be inserted into the document. It's clear that this takes up extra CPU cycles, but is it enough to have a noticeable impact on the performance of your application? We'll investigate this question in depth in the next section.

2. CSS-in-JS increases your bundle size. This is an obvious one — each user who visits your site now has to download the JavaScript for the CSS-in-JS library. Emotion is 7.9 kB minzipped and styled-components is 12.7 kB. So neither library is huge, but it all adds up. (react + react-dom is 44.5 kB for comparison.)

3. CSS-in-JS clutters the React DevTools. For each element that uses the css prop, Emotion will render <EmotionCssPropInternal> and <Insertion> components. If you are using the css prop on many elements, Emotion's internal components can really clutter up the React DevTools, as seen here:

The React DevTools displaying many internal Emotion components

The Ugly

1. Frequently inserting CSS rules forces the browser to do a lot of extra work. Sebastian Markbåge, member of the React core team and the original designer of React Hooks, wrote an extremely informative discussion in the React 18 working group about how CSS-in-JS libraries would need to change to work with React 18, and about the future of runtime CSS-in-JS in general. In particular, he says:

In concurrent rendering, React can yield to the browser between renders. If you insert a new rule in a component, then React yields, the browser then have to see if those rules would apply to the existing tree. So it recalculates the style rules. Then React renders the next component, and then that component discovers a new rule and it happens again.

This effectively causes a recalculation of all CSS rules against all DOM nodes every frame while React is rendering. This is VERY slow.

Update 2022-10-25: This quote from Sebastian is specifically referring to performance in React Concurrent Mode, without useInsertionEffect. I recommend reading the full discussion if you want an in-depth understanding of this. Thanks to Dan Abramov for pointing out this inaccuracy on Twitter.

The worst thing about this problem is that it's not a fixable issue (within the context of runtime CSS-in-JS). Runtime CSS-in-JS libraries work by inserting new style rules when components render, and this is bad for performance on a fundamental level.

2. With CSS-in-JS, there's a lot more that can go wrong, especially when using SSR and/or component libraries. In the Emotion GitHub repository, we receive tons of issues that go like this:

I'm using Emotion with server-side rendering and MUI/Mantine/(another Emotion-powered component library) and it's not working because...

While the root cause varies from issue to issue, there are some common themes:

  • Multiple instances of Emotion get loaded at once. This can cause problems even if the multiple instances are all the same version of Emotion. (Example issue)
  • Component libraries often do not give you full control over the order in which styles are inserted. (Example issue)
  • Emotion's SSR support works differently between React 17 and React 18. This was necessary for compatibility with React 18's streaming SSR. (Example issue)

And believe me, these sources of complexity are just the tip of the iceberg. (If you're feeling brave, take a look at the TypeScript definitions for @emotion/styled.)

Performance Deep Dive

At this point, it's clear that there are both significant pros and significant cons to runtime CSS-in-JS. To understand why our team is moving away from the technology, we need to explore the real-world performance impact of CSS-in-JS.

This section focuses on the performance impact of Emotion, as it was used in the Spot codebase. As such, it would be a mistake to assume that the performance numbers presented below apply to your codebase as well — there are many ways to use Emotion, and each of these has its own performance characteristics.

Serialization Inside of Render vs. Outside of Render

Style serialization refers to the process by which Emotion takes your CSS string or object styles and converts them to a plain CSS string that can be inserted into the document. Emotion also computes a hash of the plain CSS during serialization — this hash is what you see in the generated class names, e.g. css-15nl2r3.

While I have not measured this, I believe one of the most significant factors in how Emotion performs is whether style serialization is performed inside or outside of the React render cycle.

The examples in the Emotion docs perform serialization inside render, like this:



function MyComponent() {
  return (
    <div
      css={{
        backgroundColor: 'blue',
        width: 100,
        height: 100,
      }}
    />
  );
}


Enter fullscreen mode Exit fullscreen mode

Every time MyComponent renders, the object styles are serialized again. If MyComponent renders frequently (e.g. on every keystroke), the repeated serialization may have a high performance cost.

A more performant approach is to move the styles outside of the component, so that serialization happens one time when the module loads, instead of on each render. You can do this with the css function from @emotion/react:



const myCss = css({
  backgroundColor: 'blue',
  width: 100,
  height: 100,
});

function MyComponent() {
  return <div css={myCss} />;
}


Enter fullscreen mode Exit fullscreen mode

Of course, this prevents you from accessing props in your styles, so you are missing out on one of the main selling points of CSS-in-JS.

At Spot, we performed style serialization in render, so the following performance analysis will focus on this case.

Benchmarking the Member Browser

It's finally time to make things concrete by profiling a real component from Spot. We'll be using the Member Browser, a fairly simple list view that shows you all of the users in your team. Virtually all of the Member Browser's styles are using Emotion, specifically the css prop.

The Member Browser in Spot

For the test,

  • The Member Browser will display 20 users,
  • The React.memo around the list items will be removed, and
  • We'll force the top-most <BrowseMembers> component to render each second, and record the times for the first 10 renders.
  • React Strict Mode is off. (It effectively doubles the render times you see in the profiler.)

I profiled the page using the React DevTools and got 54.3 ms as the average of the first 10 render times.

My personal rule of thumb is that a React component should take 16 ms or less to render, since 1 frame at 60 frames per second is 16.67 ms. The Member Browser is currently over 3 times this figure, so it's a pretty heavyweight component.

This test was performed on an M1 Max CPU which is WAY faster than what the average user will have. The 54.3 ms render time that I got could easily be 200 ms on a less powerful machine.

Analyzing the Flamegraph

Here's the flamegraph for a single list item from the above test:

Performance flamegraph of the BrowseMembersItem component

As you can see, there are a huge number of <Box> and <Flex> components being rendered — these are our "style primitives" which use the css prop. While each <Box> only takes 0.1 – 0.2 ms to render, this adds up because the total number of <Box> components is massive.

Benchmarking the Member Browser, without Emotion

To see how much of this expensive render was due to Emotion, I rewrote the Member Browser styles using Sass Modules instead of Emotion. (Sass Modules are compiled to plain CSS at build time, so there is virtually no performance penalty to using them.)

I repeated the same test described above and got 27.7 ms as the average of the first 10 renders. That's a 48% decrease from the original time!

So, that's the reason we are breaking up with CSS-in-JS: the runtime performance cost is simply too high.

To repeat my disclaimer from above: this result only directly applies to the Spot codebase and how we were using Emotion. If your codebase is using Emotion in a more performant way (e.g. style serialization outside of render), you will likely see a much smaller benefit after removing CSS-in-JS from the equation.

Here is the raw data for those who are curious:

Spreadsheet showing render times between Emotion and non-Emotion Member Browser

Our New Styling System

After we made up our minds to switch away from CSS-in-JS, the obvious question is: what should we be using instead? Ideally, we want a styling system that has performance similar to that of plain CSS while keeping as many of the benefits of CSS-in-JS as possible. Here are the primary benefits of CSS-in-JS that I described in the section titled "The Good":

  1. Styles are locally-scoped.
  2. Styles are colocated with the components they apply to.
  3. You can use JavaScript variables in styles.

If you paid close attention to that section, you'll remember that I said that CSS Modules also provide locally-scoped styles and colocation. And CSS Modules compile to plain CSS files, so there is no runtime performance cost to using them.

The main downside to CSS Modules in my mind is that, at end of the day, they are still plain CSS — and plain CSS is lacking features that improve DX and reduce code duplication. While nested selectors are coming to CSS, they aren't here yet, and this feature is a huge quality of life boost for us.

Fortunately, there is an easy solution to this problem — Sass Modules, which are simply CSS Modules written in Sass. You get the locally-scoped styles of CSS Modules AND the powerful build-time features of Sass, with essentially no runtime cost. This is why Sass Modules will be our general purpose styling solution going forward.

Side note: With Sass Modules, you lose benefit 3 of CSS-in-JS (the ability to use JavaScript variables in styles). Though, you can use an :export block in your Sass file to make constants from the Sass code available to JavaScript. This isn't as convenient, but it keeps things DRY.

Utility Classes

One concern the team had about switching from Emotion to Sass Modules is that it would be less convenient to apply extremely common styles, like display: flex. Before, we would write:



<FlexH alignItems="center">...</FlexH>


Enter fullscreen mode Exit fullscreen mode

To do this using only Sass Modules, we would have to open the .module.scss file and create a class that applies the styles display: flex and align-items: center. It's not the end of the world, but it's definitely less convenient.

To improve the DX around this, we decided to bring in a utility class system. If you aren't familiar with utility classes, they are CSS classes that set a single CSS property on the element. Usually, you will combine multiple utility classes to get the desired styles. For the example above, you would write something like this:



<div className="d-flex align-items-center">...</div>


Enter fullscreen mode Exit fullscreen mode

Bootstrap and Tailwind are the most popular CSS frameworks that offer utility classes. These libraries have put a lot of design effort into their utility systems, so it made the most sense to adopt one of them instead of rolling our own. I had already been using Bootstrap for years, so we went with Bootstrap. While you can bring in the Bootstrap utility classes as a pre-built CSS file, we needed to customize the classes to fit our existing styling system, so I copied the relevant parts of the Bootstrap source code into our project.

We've been using Sass Modules and utility classes for new components for several weeks now and are quite happy with it. The DX is similar to that of Emotion, and the runtime performance is vastly superior.

Side note: We're also using the typed-scss-modules package to generate TypeScript definitions for our Sass Modules. Perhaps the largest benefit of this is that it allowed us to define a utils() helper function that works like classnames, except it only accepts valid utility class names as arguments.

A Note about Compile-Time CSS-in-JS

This article focused on runtime CSS-in-JS libraries like Emotion and styled-components. Recently, we've seen an increasing number of CSS-in-JS libraries that convert your styles to plain CSS at compile time. These include:

These libraries purport to provide a similar benefits to runtime CSS-in-JS, without the performance cost.

While I have not used any compile-time CSS-in-JS libraries myself, I still think they have drawbacks when compared with Sass Modules. Here are the drawbacks I saw when looking at Compiled in particular:

  • Styles are still inserted when a component mounts for the first time, which forces the browser to recalculate the styles on every DOM node. (This drawback was discussed in the section title "The Ugly".)
  • Dynamic styles like the color prop in this example cannot be extracted at build time, so Compiled adds the value as a CSS variable using the style prop (a.k.a. inline styles). Inline styles are known to cause suboptimal performance when applied many elements.
  • The library still inserts boilerplate components into your React tree as shown here. This will clutter up the React DevTools just like runtime CSS-in-JS.

Conclusion

Thanks for reading this deep dive into runtime CSS-in-JS. Like any technology, it has its pros and cons. Ultimately, it's up to you as a developer to evaluate these pros and cons and then make an informed decision about whether the technology is right for your use case. For us at Spot, the runtime performance cost of Emotion far outweighed the DX benefits, especially when you consider that the alternative of Sass Modules + utility classes still has a good DX while providing vastly superior performance.

About Spot

At Spot, we're building the future of remote work. When companies go remote, they often lose the sense of connection and culture that was present in the office. Spot is a next-gen communication platform that brings your team together by combining traditional messaging and video conferencing features with the ability to create & customize your own 3D virtual office. Please check us out if that sounds interesting!

P.S. We're looking for talented software engineers to join the team! See here for details.

A picture of Spot

This post was also published on the Spot blog.

Comments 146 total

  • Zenobius Jiricek
    Zenobius JiricekOct 17, 2022

    To be honest, the whole "it's hard to tell what's used by what" in plain CSS is already solved with BEM.

    BEM scales very nicely.

    So by using new CSS variables with CSS modules to create locally scoped overrides you can still create runtime themed styles.

    Yes your "frontend" Devs will need to learn CSS instead of just treating it like the Typescript autocomplete object faceroll experience that current cssinjs promotes.

    Other unpopular opinions I hold:

    Nested CSS rules made popular by lesscss are a maintenance nightmare. Don't use them.

    Extends is also bad.

    Mixins... Yep I hate them too.

    • Julian Soto
      Julian SotoOct 17, 2022

      Sass paved the way but it's time to ditch it.

    • leob
      leobOct 17, 2022

      Always good to see some contrary opinions - not everything that "shines" is made of gold :)

    • Enrique Ballesté Peralta
      Enrique Ballesté PeraltaOct 17, 2022

      Same (sorta), I will forever be in love with SASS and BE (BEM without the modifiers).

      I satisfy all 3 of of the mentioned pro's of CSS in JS with: SASS+BE, custom HTML elements, and CSS variables.

      I approach everything as a component and use custom HTML elements with a corresponding sass file with mixins only used to tap into our global design systems like our grids/buttons/fonts.

      To me using BEM's modifier classes are equally as ugly (and annoying .block__element--modifier??? blegh!) as using utility classes and I absolutely hate referencing them in my JavaScript files. I restrict the use of modifiers for side-effects of user interactions which are modified via JS so I use custom HTML attributes as modifiers instead and do not worry about it bleeding into other components since I am taking advantage of SASS's locally scoped nature. I also keep SASS Elements one level deep in terms of nesting. My SASS components are usually flat and I only nest when doing something like the lobotomized owl.

      For getting colors or dimensions from JS into the stylesheet, i inject custom css variables into the DOM element's style attribute and reference it in the css:

      <CustomHTMLEl>
        <div class"CustomHTMLEl__container">
          ...content
        </div>
      </CustomHTMLEl>
      
      Enter fullscreen mode Exit fullscreen mode
        const $customEl = document.querySelector('CustomHTMLEl');
        const bgColor = '#32fa45';
      
        if (!$customEl) { return; }
        $customEl.style.setProperty('--bgColor', bgColor);
      
      
        const $hideBtns = document.querySelectorAll('HideCustomElBtn');
      
        if (!$hideBtns.length) { return; }
        $hideBtns.forEach(($btn) => {
          $btn.addEventListener('click', (e) => {
            $customEl.setAttribute('hidden', true);
          });
        });
      
      Enter fullscreen mode Exit fullscreen mode

      and in your SASS:

      CustomHTMLEl {
        display: block;
        background: var(--bgColor);
        padding: 40px 0;
      
        @include media('>=md') {
          padding: 80px 0 120px;
        }
      
        &[hidden] {
          display: none;
        }
      
        .CustomHTMLEl {
          &__container {
            @include grid-container;
            grid-template-columns: 1fr;
          }
      
          &__content {
            display: flex;
            flex-direction: column;
            align-items: center;
      
            > * + * {
              margin-top: 16px;
            }
          }
        }
      }
      
      Enter fullscreen mode Exit fullscreen mode

      Works beautifully.

      • Zenobius Jiricek
        Zenobius JiricekOct 21, 2022

        I don't care about things being ugly.

        My main goal is scaling in large teams and maintenance long term, so I care more about understanding dependencies and avoiding the cascade.

        Looks like you've also dived deep into the problem 👍️

        • Enrique Ballesté Peralta
          Enrique Ballesté PeraltaOct 25, 2022

          i guess it's the designer in me, but, to me, if it ain't beautiful, it ain't worth looking at.

      • Zenobius Jiricek
        Zenobius JiricekOct 21, 2022

        Another thing I would mention is that your nesting of media queries is also a thing i loathe.

        the pattern I follow is :

        thing/
          index.scss
          thing.scss
          thing-mediumup.scss
          thing-largeup.scss
          thing-xxxxxxxxxxxxlargeup.scss
        
        Enter fullscreen mode Exit fullscreen mode

        thing/index.scss

        @import "./thing.scss";
        
        @include media('>=md') { 
          @import "./thing.mediumup.scss";
        }
        @include media('>=lg') { 
          @import "./thing.largeup.scss";
        }
        @include media('>=xxxxxxxxxxxxlg') { 
          @import "./thing.xxxxxxxxxxxxlargeup.scss";
        }
        
        Enter fullscreen mode Exit fullscreen mode

        This way, you're following a mobile first approach and then you don't end up with this :

        CustomHTMLEl {
          display: block;
          background: var(--bgColor);
          padding: 40px 0;
        
          @include media('>=md') {
            padding: 80px 0 120px;
          }
        
          &[hidden] {
            display: none;
          }
        
          .CustomHTMLEl {
            &__container {
              @include grid-container;
              grid-template-columns: 1fr;
              @include media('<=md') {
                padding: 80px 0 120px;
              }
            }
        
            &__content {
              display: flex;
              flex-direction: column;
              align-items: center;
        
              > * + * {
                margin-top: 16px;
                @include media('<=sm') {
                  padding: 80px 0 120px;
                }
              }
            }
          }
        }
        
        Enter fullscreen mode Exit fullscreen mode

        It's a pretty simple example, but I've seen worse.

        • Marko A
          Marko AOct 24, 2022

          Nesting media queries is one of the reasons I still love and use SASS. It allows me to immediately see all behavior for a single piece of styling. I don't have to go into 3-4 files (like in your case) in order to see how this component behaves on each resolution.

          But to each their own, we all have our own preferences when it comes to some of this stuff. To me personally it's just easier to piece all of this stuff together when it's all in the same file, I can just immediately see "Oh this selector behaves like this on mobile, after 'md' and after 'lg' breakpoints".

          • Enrique Ballesté Peralta
            Enrique Ballesté PeraltaOct 25, 2022

            same! multiple sass files for a single component? seems like a step backwards to me, harder to see the whole picture.

            • Salvatore Tedde
              Salvatore TeddeOct 25, 2022

              With this pattern you don't know how a component behaves on different breakpoints. If the component is complex is even more so, since when you change one element you have to check 4 files to make sure that it works as you intended. I highly discourage this pattern, nested media queries seems the natural way of styling a component IMHO.

              • Jasperrr91
                Jasperrr91Oct 25, 2022

                Indeed, it's much easier to work with media queries that are simply called whenever they needed on a small bit of styling; than splitting entire stylesheets based on those same media queries.

                Yes, you might up with a few more media queries but at least it's very easy for each developer to see what's going on. Having to look into separate files for the styling of a single component, as well as remembering what's happening for every single viewport, is a tremendous pain and unscalable nor maintainable.

                • Enrique Ballesté Peralta
                  Enrique Ballesté PeraltaOct 26, 2022

                  also, they implied that we aren't doing mobile first using this approach which we 100% are and it's very easy to see that we are.

                  if you are a stickler for order then you would start from the bottom up and declare the padding at the mobile level and later reset for wider devices

                      &__container {
                        @include grid-container;
                        grid-template-columns: 1fr;
                        padding: 80px 0 120px;
                        margin-top: 16px;
                  
                        @include media('>=md') {
                          padding: 0;
                          margin-top: 24px;
                        }
                      }
                  
                  Enter fullscreen mode Exit fullscreen mode

                  but I also don't mind the convenience of using include-media to specifically target mobile if I don't want to use the bottom up approach. it isn't hard at all to reason about and I use it sparingly, only when I know it's going to be mostly mobile only styles.

                      &__container {
                        @include grid-container;
                        grid-template-columns: 1fr;
                        margin-top: 16px;
                  
                        // mobile only
                        @include media('<md') {
                          padding: 80px 0 120px;
                        }
                  
                        @include media('>=md') {
                           margin-top: 24px;
                        }
                      }
                  
                  Enter fullscreen mode Exit fullscreen mode
      • Hardik Shah
        Hardik ShahAug 29, 2023

        Hello @eballeste ,

        Few questions on your implementation -

        1. Are you using custom elements to create your own Web components or using customized custom elementsi.e. class MyButton extends HTMLButtonElement and customElements.define() takes a third argument - {extends: 'button'}?

        2. Do your custom elements work perfectly fine in Safari?

        3. In your custom elements, how are you rendering the markup? Are you using innerHtml (vulnerable to injection attacks) for two-way binding, or a better option?

        4. This is a unrelated question to your stack. I am using EmotionJS in my React component library, how can I migrate my EmotionJS styles to SASS styles?

    • Daniel Staver
      Daniel StaverOct 17, 2022

      I love BEM and it's what I use when writing regular CSS for my own use.

      The problem with BEM isn't BEM itself, it's getting your coworkers, new developers
      and external agencies to understand and follow the syntax. We implemented BEM at a previous workplace and it worked perfectly until some core developers quit and new ones were hired, and then the styles just quickly deteriorated.

      CSS modules and CSS-in-JS are locally scoped by default, and you have to specifically add syntax to make things global. As much as I dislike Tailwind syntax, that too has the advantage of keeping styling local to the place it's applied at least.

      • Bauke Regnerus
        Bauke RegnerusOct 20, 2022

        Did the new developers read the BEM documentation? In my experience a lot of people find that BEM "looks ugly" at first sight and then don't even try to understand it. While it solves so many problems.

      • Zenobius Jiricek
        Zenobius JiricekOct 21, 2022

        There's linting you can use to enforce this.

      • Marko A
        Marko AOct 24, 2022

        The problem with BEM isn't BEM itself, it's getting your coworkers, new developers and external agencies to understand and follow the syntax

        This is my experience as well. BEM can scale really well, but keeping it consistent is a problem when you have multiple people working in a codebase. I mean same can be said for any tool. Getting everyone on board is usually the hardest part.

        People are also detracted from BEM because of it's syntax and because it "looks ugly" to them, but once you actually start using it, your CSS becomes a breeze to use and read because its "standardized" in some way. It also produces flat styling without horrible nesting which is a big plus in my book. Simpler flat styling allows easier per situation override if needed and you don't have to battle CSS selector nesting and specificity that much at all.

        When using BEM, I've usually went with BEM + utility classes approach. Some more common things like spacing (padding, margin), colors, and maybe some flex/block properties would be mostly in utility classes and everything else would be BEM. It worked quite nicely for projects that I've been working on. It also allowed me to build UI faster because I wouldn't have to apply same spacing values all over again for each component, but could just apply a utility class name to some wrapper element and get the spacing that I want.

        These days when working with React, the projects that I usually work on use SASS Modules and that also scales nicely. It's easier to get people on the same page because there is no specific naming convention like BEM and people don't have to worry about naming things because everything is locally scoped. This approach works nicely in most situations except few edge cases where styles need to be shared or something kinda needs to be global, then it's not so elegant anymore and in a single file. But other than that, it works really well and I don't have to leave comments on PRs like "hey the name of this class doesn't adhere to BEM principles, it should be this and that etc."

        I still prefer SASS Modules compared to CSS-in-JS (emotion, styled-components) because you are still "mostly" writing just regular CSS that is closer to the native web.

        I'm also working on a project that uses styled-components and even though I do like type safety we get with that because we use Typescript, it also makes every little styling its own component and to see any conditional logic for styling I always have to jump to source of a styled component to see what's going on. At least with SASS Modules and conditionally applying classNames I can immediately see what class is being applied for a certain condition and just by its name I can probably tell what it does without having to go into its source.

        But that's just me and my personal experience.

        • Dwayne Crooks
          Dwayne CrooksNov 14, 2024

          When you refer to Sass Modules are you referring to this. How does that relieve you of having a specific naming convention like BEM? I use the new module system extensively but I still find the need for BEM. For e.g. I'm using the module system and BEM here. To me if in one module (say the button module) you have a class named .primary and in another module (say the link module) you also have a class named .primary then those classes will class with each other once the Sass is compiled to CSS. Can you elaborate more on that?

          • Marko A
            Marko ANov 14, 2024

            Sure. I'm referring to CSS Modules, but just using SASS files because I still love SASS nesting features github.com/css-modules/css-modules

            It means you can import style files directly into the component file and classes will he hashed in some way to make them unique. That way you can have classes like .primary multiple times, but they won't clash because they are locally scoped by name.

    • András Tóth
      András TóthOct 19, 2022

      Mixins... I FKN love them. They are excellent at naming certain CSS "hacks": things that only make sense together, things that must act together otherwise the effect you want won't happen. Now you could write comments around it, but named mixins equate well-named, descriptive function names from clean coding.

      • Zenobius Jiricek
        Zenobius JiricekOct 21, 2022

        if they could be strongly typed I might agree with you, but all they end up doing is hiding complexity.

        • András Tóth
          András TóthOct 24, 2022

          Sorry, I don't get it. Do you have an example?

    • Ajinkya Borade
      Ajinkya BoradeOct 20, 2022

      Good old BEM days with Knockout JS, not many React devs would know about BEM styling

      • Zohaib Ashraf
        Zohaib AshrafOct 22, 2022

        BEM is really good i learnt from jonas sass course and it was really fun utilizing block, element and modifier classes

    • Allan Bonadio
      Allan BonadioOct 21, 2022

      "Nested CSS rules ... are a maintenance nightmare."

      Use things they way they're intended. If css rules are only intended to work in, say, tables, then nest them inside of tables. Or, just certain classes of tables. If they are to be general, then don't nest them.

      table.french {
          .hilite { color: orange; }
      }
      table.italian {
          .hilite { color: green; }
      }
      .hilite { color: blue; }
      
      Enter fullscreen mode Exit fullscreen mode

      These three .hilite classes will not conflict with each other; the deeper ones will always override the shallow one at the bottom, but only inside .french or .italian tables. Hilite will be orange in french tables; green in italian tables, and blue everywhere else in your whole app. If you want .hilite to do something else outside of your zoot-suit panel, write it that way:

      .zoot-suit {
          table.french {
              .hilite { color: orange; }
          }
          table.italian {
              .hilite { color: green; }
          }
          .hilite { color: blue; }
      }
      
      Enter fullscreen mode Exit fullscreen mode

      Any HTML outside of the .zoot-suit panel will have to make their own style rules; they can use .hilite freely without any orange, green or blue bleeding in.

      • Marko A
        Marko AOct 24, 2022

        Only issue I have with this is that then you don't know if .hilite is something global, is it a utility class, is it something specific to that table only when you encounter it in HTML/JSX. If using .table__cell--hilite for example you know that it's a highlighted table cell and you can get all that data without having to look at the source code. And you know that it should be a table only thing here since it's kinda namespaced with table__

        Also, your example uses same class name for global selector and then selector that is more specific to 2-3 scenarios which now makes that selector super situation specific and it doesn't necessarily convey its meaning just by reading it. In my opinion it also raises CSS selector specificity unnecessarily.

        But to each their own, I have my preferences and you have yours. Lot of this stuff is of course also situational and cannot really be applied to 100% of projects/situations.

    • Halfist
      HalfistOct 27, 2022

      To me both HTML-in-JS and CSS-in-JS look ugly, because you can't separate presentation from logic, hence can't divide this work between different developers without permanent merge conflicts.

  • Julian Soto
    Julian SotoOct 17, 2022

    Sass Modules + utility classes still has a good DX while providing vastly superior performance

    U sure about that? Sass still uses javascript after compilation. And about, the DX, sass is kinda painful to work with.

    • Sam Magura
      Sam MaguraOct 18, 2022

      I'm not sure what you mean when you say that "Sass still uses JavaScript after compilation". Unless you are referring to that fact that Sass Modules are compiled to a CSS file and a very small JS module which maps the human-readable class names to mangled class names.

  • Derek D
    Derek DOct 17, 2022

    Have you ever looked into Vue's style system? I'm not sure how they do it, but you simply add a "scoped" attribute to a style tag and it automatically scopes the CSS to the components. Their CSS is also compiled at build time into its own file without the need to render separate components. This is the one thing that kept me from switching to React for years. Their styling just works and it works really well.

    I haven't messed with Vue 3's implementation, but they also allow you to use JS variables, which are rendered as inline styles: vuejs.org/api/sfc-css-features.htm...

    • Carl Lidström Dreyer
      Carl Lidström DreyerOct 17, 2022

      Vue adds a hashed attribute to the dom element, for example:
      data-v-ikd87k9.

      Then your CSS is transformed using PostCSS to target CSS-selectors with your attribute, for example:
      .my-class[data-v-ikd87k9] {}

      • Clay Ferguson
        Clay FergusonOct 18, 2022

        I consider it a train-wreck when I can't view my DOM and see all the class names. I don't like my class names being mangled like that hash thing you mentioned. I'm fine with SCSS and not having styles scoped to components, because lots of my styles are cross-component anyway.

        • Carl Lidström Dreyer
          Carl Lidström DreyerOct 19, 2022

          I agree, personally, I consider a readable DOM to be important, which is why I try to avoid Tailwind :)
          However, I've never experienced Vue's hashing to have impacted this negatively.

          • Lars Rye Jeppesen
            Lars Rye JeppesenOct 20, 2022

            What about Tailwind is not readable? If anything it's more readable.

            And nothing prevents you from making custom classes composed of Tailwind helper classes.

            • Mattan Ingram
              Mattan IngramOct 22, 2022

              Tailwind is very difficult to read when you get into more complex cases, especially when working on teams where not everyone knows Tailwind utilities. Having a class that actually describes what the purpose of the element is in addition to styling it is far more readable, takes up far less space, and can still allow for the use of utility classes where they are appropriate for example adding layout and margins.

              Vue does not mess with the class names you add to elements, it just appends a data-hash attribute.

    • Lars Rye Jeppesen
      Lars Rye JeppesenOct 20, 2022

      Angular does this by default.
      Very very nice

  • leob
    leobOct 17, 2022

    Fantastic ... this article might have saved me from unknowingly going down a road that I really shouldn't go down, at some point ... saving this article in a place where I'll be able to find it again.

  • Ivan Jeremic
    Ivan JeremicOct 17, 2022

    Charka-UI sweating right now reading this article😂

    • Sam Magura
      Sam MaguraOct 18, 2022

      Hahahahah

    • Primo Sabatini
      Primo SabatiniOct 20, 2022

      I don't think so. Full disclosure I was a contributor on Chakra-UI.

      Sage has been aware of the issues with Chakra and it's in their doc. He's been working on ZagJS which uses state machines to provide unstyled components. I assume that one could use CSS, Tailwind or CSS-in-JS to style those components. This is a YouTube video of Sage talking to Lee Robinson about The Future of Chakra/ZagJS.

      While I agree with the author about CSS-in-JS, being "the 2nd most active maintainer of Emotion", he's been a contributor to the problem and previously thought of as a contributor to THE solution. We've all been there. If you haven't yet, just wait.

      Shouldn't React with their vdom be sweating given projects like Svelte, Solid and Qwik? If you laugh at that remember that once JQuery and Angular JS ruled the world. They were each great for their time, like Emotion, but eventually every king falls.

      • Yi Kan Mario Mui
        Yi Kan Mario MuiJan 3, 2023

        What exactly is Svelte and Solid except the idea that surgical dom mutations are better than the complexity of a reconciliation engine. Now that we have modern optimized browsers, the svelte/solid model makes sense. A really big app with lots of dashboards and data feeds...do you really want that to be in svelte? Hrm.

        There's also module federation where you sortah want css in js or you'd have to manually include a copy of extracted css into every federated module.
        And you have to worry about tree shaking when its a .css file.
        not that that's a big deal but it's an additional worry.

        You could make the similar argument of using emotion to do dynamic css, and then using tailwind utility classes for the majority of your use case. Then you could have your cake and eat it too.

  • Gianluca Frediani
    Gianluca FredianiOct 17, 2022

    I think that Vanilla Extract is a good option that has many of the advantages and none of the cons. It is basically a CSS precompiler like SASS, but using TypeScript as a language. All the classes are generated at build time, the only runtime cost is choosing the right class (but you must do the same with SASS or plain CSS)

  • Renan Ferro
    Renan FerroOct 17, 2022

    Nice article man, this helped me how to improve the implementation in Angular too!

  • Martin
    MartinOct 17, 2022

    Thank you for sharing your insights. I agree (judging from my current view point of past and present projects and experiences) that CSS-modules paired with SASS/SCSS are the cleanest and sanest approach to component styles.
    But I would say it also checks the third point of your three-point list of good things (locally-scoped styles, co-location, JavaScript variables in styles):
    You can share values between styles and js code, in both directions, during build time and during runtime. Each scenario requires its own mechanism (I want to cover this in a blog post but don't know when I will find the time), here is the gist of it:

    • from css to js, build time: use :export
    • from css to js, runtime: use getComputedStyle()
    • from js to css, build time: use sass-loader additionalData option
    • from js to css, runtime: use element.style.setProperty('--some-prop', value)

    In my webpack config I share the colors (in a *.ts file) with my styles by generating variables like $primary-main:

    import { colors } from './colors';
    import { jsToScss } from './js-to-scss';
    
    {
      loader: 'sass-loader',
      options: {
        additionalData: jsToScss(colors),
      },
    },
    
    Enter fullscreen mode Exit fullscreen mode
  • Zain Wania
    Zain WaniaOct 17, 2022

    Overall fantastic analysis on css-in-js, learned alot for sure and appreciate the time you took to analyze everything.

    I am struggling to understand how you reached your conclusion for picking bootstrap though. while bootstrap does have utility classes its main use case is telling your app how it ought to be.

    Tailwind intends to provide a utility class framework with basic defaults. Tailwind does NOT enforce any styling on your components like bootstrap does with things like its btn class

    If you are morphing bootstrap into something to fit your design system i believe you would be much better off creating a design system in Tailwind instead.

    if you have more info on why you picked bootstrap besides that you were familiar with it, i would love to understand further because everythinbg described including the ovverides you are doing to bootstrap scream at me that you would be better off with tailwind

    • Sam Magura
      Sam MaguraOct 18, 2022

      We're not using all of Bootstrap, just their utility classes (which we have customized quite a bit). I'm not very familiar with Tailwind but I think they may have taken utility classes a bit too far.

      • niddy - the animated atheist
        niddy - the animated atheistOct 19, 2022

        Tailwind will now tree-shake its styles down on build, removing all unused styles.

        • Jonas
          JonasOct 20, 2022

          Afaik they don't tree-shake anymore. The default for quite some time now is the JIT runtime, which only creates the CSS rules that are needed in the first place :)

      • Lars Rye Jeppesen
        Lars Rye JeppesenOct 20, 2022

        You will absolutely love Tailwind, trust me bro

        • Humoyun Ahmad
          Humoyun AhmadOct 24, 2022

          Bro, I personally used Tailwind and I can say it is really good solution but... it is really good solution for only a specific parts of styling, namely, for utility styles (mostly common & repetitive styles like positioning, padding, margins, flex, ...). But applying Tailwind for everything is a mess and vastly clutters codebase. Besides, it has steep learning curve (it may not be felt while you are working solo but in a team with noticeable numbers you can really see this). It has its own place in DX but it is definitely not a silver bullet.

          • Lars Rye Jeppesen
            Lars Rye JeppesenOct 24, 2022

            Wether it clutters the codebase or not is up to you.
            There is nothing preventing you from making your normal classes and use the @apply directive.

            .myClass {
            @apply rounded-full mx-auto text-primary-500 my-8
            }

            So in that regard there is no difference.

            • Humoyun Ahmad
              Humoyun AhmadOct 24, 2022

              Yes. And try to manage hundreds or thousands of this kind of meta classes across a huge codebase. You are adding yet another unnecessary abstraction layer on top of already provided abstraction... Don't think in terms of just you coding by yourself and setting conventions on how to use it, maybe it works to some degree, but not in a team .

  • Anthony Oyathelemhi
    Anthony OyathelemhiOct 17, 2022

    You should've just went with Tailwind tbh

    Bootstrap was just convenient because you were already familiar with the classes but there's so much you could've gained by using Tailwind, like JIT compilation (on demand one-time utility classes, variants for element state and media queries, ability to change things using a configuration file, etc)

    • Lars Rye Jeppesen
      Lars Rye JeppesenOct 20, 2022

      And Tailwind is not opiniated, unlike Bootstrap.

      110pct agree with all your points

  • Rense Bakker
    Rense BakkerOct 17, 2022

    Why people keep hyping tailwind is beyond me. Nothing about compiling a list of utility classes before you run your code, sounds at all useful to me... It literally prevents you from doing the easiest dynamic stuff because of the limitations of your utility classes and the only way around that is to do custom DOM manipulation to change css variables at runtime. Why do people still think this is a good idea? Why not use the benefits of css in js and have it compile to plain css files when building? Seems like a much more sensible and maintainable solution to me. Also, having all those utility classes that you don't use, creates far more overhead... CSS files have to be downloaded by the browser too... You literally have code in production that never gets used... I thought we moved past that :(

    • iohansson
      iohanssonOct 19, 2022

      Sorry, but your argument about having unused utility classes is incorrect. Tailwindcss for a long time now compiles only the classes that are actually being used in your code.
      Would be great to fact-check your arguments before you post since it might mislead those not familiar with the topic.

      • Rense Bakker
        Rense BakkerOct 20, 2022

        Ok, what about the other argument?

        • iohansson
          iohanssonOct 20, 2022

          I've been using tailwind with Vue mainly, for dynamic stuff you would just conditionally apply different utility classes, no need to use CSS vars. Can't complain about the DX.
          The only negative side I can see here is that in order to reuse your components in another project that project would need Tailwindcss set up as well, so they're not as portable. But that might also be true for css-in-js solutions, right?
          In this sense web components would be the best probably, I might be wrong though.

          • Rense Bakker
            Rense BakkerOct 22, 2022

            You cant conditionally apply dynamic values. You also cannot access defined constants in tailwind with JavaScript, so if you need to apply custom logic based on theme breakpoints you have to define and manage them in two places.

            • Zenobius Jiricek
              Zenobius JiricekApr 5, 2023

              You also cannot access defined constants in tailwind with JavaScript

              By constants, do you mean tokens? because I experimented with tailwind in a project recently, it was ok. but i definetly appreciate the css-in-js route more these days.

              Anyway I generated tokens from my config with this prelaunch tool:

              #!/usr/bin/env node
              // code: language=javascript
              
              const fs = require("fs");
              const path = require("path");
              const resolveConfig = require("tailwindcss/resolveConfig");
              const prettier = require("prettier");
              
              const tailwindConfig = require("../tailwind.config");
              
              const { theme } = resolveConfig(tailwindConfig);
              const themeStr = JSON.stringify(theme, null, 2);
              
              try {
                fs.mkdirSync(".generated/tailwind-tokens", { recursive: true });
              
                // write the file to src/theme.js after
                // having prettier format the string for us
                fs.writeFileSync(
                  path.resolve(process.cwd(), ".generated/tailwind-tokens/package.json"),
                  prettier.format(
                    JSON.stringify({
                      name: "dot-tailwindtoken",
                      private: true,
                      main: "./tokens.ts",
                    }),
                    { parser: "json" }
                  ),
                  "utf-8"
                );
                fs.writeFileSync(
                  path.resolve(process.cwd(), ".generated/tailwind-tokens/tokens.ts"),
                  prettier.format(`export const tokens = ${themeStr} as const`, {
                    parser: "typescript",
                  }),
                  "utf-8"
                );
              } catch (err) {
                // uh-oh, something happened here!
                console.log(err.message);
              }
              
              Enter fullscreen mode Exit fullscreen mode

              and because i was using yarn 4, in the package.json :

              ...
                  "dot-tailwindtokens": "link:.generated/tailwind-tokens",
              ...
              
              Enter fullscreen mode Exit fullscreen mode

              and then i just made a simple provider

              
              import { tokens } from "dot-tailwindtokens";
              
              export type Tokens = typeof tokens;
              
              type Theme = undefined | "light" | "dark";
              
              type InitialContext = {
                tokens: typeof tokens;
                theme: Theme;
                setTheme: (theme: Theme) => void;
              };
              
              const ThemeContext = createContext<InitialContext>({
                tokens,
                setTheme: () => {
                  return;
                },
                theme: undefined,
              });
              
              ...
              
              Enter fullscreen mode Exit fullscreen mode
    • Magne
      MagneJan 12, 2023

      the only way around that is to do custom DOM manipulation to change css variables at runtime

      I also thought so, but you can actually avoid custom DOM manipulation through changing a single theme class via your declarative rendering library (React):

      youtube.com/watch?v=TavBrPEqkbY

      When you do that, the top level component with that theme class will re-render with a new theme class (but none of it's children will need to re-render since their props didn't change so the React VDOM diff will exclude them). Then, due to the theme class being a CSS variable, all the local styles scattered about the app, that reference the CSS variable, will update with the new theme during the CSS cascade. That way, you avoid the rendering de-optimization that you'd otherwise have if you changed all the local styles through re-rendering the entire app with JS.

  • Lewis Cowles
    Lewis CowlesOct 17, 2022

    Told people this for over half a decade. It is detectable by simple logic. What I'm most annoyed about is that emotion isn't CSS, it actively removes the C from CSS. You honestly might as well write a custom c++ or java frontend. And let's not forget class mangling. The whole SPA movement was a mistake

    • Baptiste Firket
      Baptiste FirketOct 21, 2022

      And why do you think one would want to remove the C from CSS?

      Yeah sure, there has never been any problem with CSS, let's write all our CSS in a single file like in good all days.

  • codebytesfl
    codebytesflOct 17, 2022

    Great article with great points. My company made a very large app with Styled Components and we are slowly moving away from it. The cons outweigh the pros. We're currently moving towards tailwinds. In our tests it has been easy to learn, and has reduced our bundle by quite a bit. We also find ourselves being more "productive" as team, meaning we're able to finish features faster which our managers like.

  • Nikola Stojaković
    Nikola StojakovićOct 17, 2022

    This is one of the reasons why I love Svelte. Styles are scoped, they're available right there at the component (.svelte) file and the compiler will warn you in case you have unused styles.

  • Markus Mathieu
    Markus MathieuOct 17, 2022

    There are some truthfull insights, but all in all it falls miles to short of the truth.
    The concept of utility classes is against the nature of the web, Tailwind, Bootstrap and more or less all of the JS-Frameworks are mostly abominations. Learning and using basic web concepts and technologies the way they are menat to be used is the way to go.
    All of this might have had some use and rectificatoin when IE was still around like jQuery had. The only vaid use of the technology you promote is for fast prototyping - if at all. The overhead and loss of control through unneccessary dependencies in production environments is just irresposible.

  • Luís Braga
    Luís BragaOct 18, 2022

    How about Linaria?

  • Pavel Laptev
    Pavel LaptevOct 18, 2022

    Good article. CSS-in-JS it's a restriction, there are many things you can't do with it. Use CSS modules and SASS. If you need to add js variables, use styles.

  • Mikael Brassman
    Mikael BrassmanOct 18, 2022

    There is a little error in your assertion that you can't use nested selectors in CSS modules. You actually can, PostCSS Preset Env has been able to post-process spec compliant nested selectors (using the & combinator) for years.

    • Mehdi M.
      Mehdi M.Oct 19, 2022

      postcss-preset-env is among the best thing that happened to CSS tooling. Game changer for me since 3 years+.

      I’m almost shocked that the possibility to leverage PostCSS is not even mentioned in the article as a huge pro towards dropping CSS-in-JS.

  • Apperside
    AppersideOct 19, 2022

    I have always been an hater of css/scss because of the complexity of maintenance when your app grows. I also always been a big fan of typesafe code, and then I started used styled components, and it was a big boost in dev experience, but then I started dealing with the runtime tradeoff excellently analised in this article. My big find was tamagui.dev, which uses strongly typed styles props which are compiled into css at build time, it is really awesome!

  • Ellis
    EllisOct 19, 2022

    On one hand I understand that one encounters problems which have to be solved "now".

    But on the other hand, "personally" and emotionally, I think many problems should and will get resolved by optimisers and architectural fixes, and not by us having to zig-zag from tool to tool only for speed etc.

    For instance, React has had issues. The guys are working and have been fixing many of them. E.g. a lot of developers filled their code with useMemo() everywhere, and it seems soon an optimiser will make that unnecessary :o)

  • Tom Raviv
    Tom RavivOct 19, 2022

    We've reached a lot of similar conclusions here at Wix.com too. Our first HTML editor (and following versions) used an in-house CSS-in-JS solution, but eventually the performance costs just got too high.

    I'm the team leader working on Stylable, our solution to this problem space. Stylable is a CSS superset that adds a type-system, automates the BEM scoping part, and provides templating tools to improve developer experience and reduce code duplications.

    We've been battle testing it internally at Wix for several years now (used in over 50+ million sites), and are now beginning to reach out to new users and to build our community. We'd love to talk if this sounds interesting to you.

    • Sam Magura
      Sam MaguraOct 19, 2022

      Hey @tomrav, Stylable looks cool. It would be interesting to hear what benefits it provides over something like Sass Modules. E.g. what problems does Stylable solve that are harder to address with existing tools?

      • Tom Raviv
        Tom RavivOct 19, 2022

        Glad you liked what you saw, I'll elaborate a bit on our approach.

        Sass and CSS modules are both well proven and battle tested solutions that we absolutely love, but have their downsides too. I would say the most notable differences between Sass modules and Stylable are the following:

        • Sass is oriented for CSS and document based projects in its core. CSS modules tries to bend Sass' approach to be more component oriented. Stylable was built with components being treated as first class citizens, allowing stylesheets or their parts to be utilized by our module system in various ways.

        • The namespacing that CSS modules provides is there to prevent collisions, and not improve readability or debugging experience (unlike BEM, which in my opinion does both). While Stylable combines both approaches, opting to use a configurable automatic BEM-like namespacing for its output.

        • At Wix a single component can have many drastically different use cases - to provide flexibility for such components, Stylable encourages treating your component styling as just another part of its API. Each stylesheet exposes its inner parts allowing to customize and theme any part of the application easily from the outside.

        • Stylable adds a type-system to CSS (currently, only at the selector level, with more of the language to come). This allows defining inheritance and relationships across stylesheets, allowing our custom language service a much better understanding of the code, resulting in advanced diagnostics, auto-completions, code navigation and other language features.

        • Lastly Stylable namespaces a lot more than just class names, also namespacing keyframes, custom properties, layers and (very soon) container queries to keep you safe from leaks or collisions.

        I'll be happy to answer any questions here, or chat over zoom sometime if you'd like to hear more.

        • Sam Magura
          Sam MaguraOct 22, 2022

          @tomrav That sounds pretty awesome! I agree the BEM-style class names would help a lot with "debuggability" over the class names you get from CSS Modules.

          It sounds like you have some pretty complex components at Wix. Or rather, you have components that need to be fully themable and work in many different contexts. We don't really have this problem at Spot so I am not sure if Stylable would be as necessary for us.

          The additional namespacing features sound nice too. That is one thing that could make Sass hard to scale (though I'm not necessarily up to date on the @use directive in Sass, I still use @import.)

          Feel free to email me if you'd like to talk more. srmagura(AT)gmail.com :)

          • Tom Raviv
            Tom RavivOct 25, 2022

            In Wix we definitely have some complex cases, but (as biased as I am), I feel that Stylable also offers a lot of value to simpler projects by providing an improved developer experience with type safety.

  • Laode Muhammad Al Fatih
    Laode Muhammad Al FatihOct 19, 2022

    Any idea about the generation of the SCSS Module Definition?

    Option 1
    Run the watch mode that typed-scss-modules have and target all SCSS files using the glob pattern within the project folder.

    Option 2
    Use the Typescript plugin called typescript-plugin-css-modules that will automatically generate SCSS Module Definition without running anything when using VSCode.

    • Sam Magura
      Sam MaguraOct 19, 2022

      We are happy with typed-scss-modules. There are benefits to using a standalone process rather than a VS Code plugin, such as:

      • You can run typed-scss-modules in your CI pipeline.
      • Developers on your team can use editors other than VS Code.
  • Benjamin Lebron
    Benjamin LebronOct 19, 2022

    One nugget of css goodness is this talk by Andy Bell that shows an alternative way of thinking about css. One of his points is that css-in-js is kinda not needed and I sorta agree

    dev.to/hankchizljaw/keeping-it-sim...

    Video:
    https://www.youtube.com/watch?v=byAaV3sy5qc&t

  • Victor Vincent
    Victor VincentOct 19, 2022

    I honestly disagree both with the article and some of the comments here. I'd like to address the first of the article. (Respect for the article, it does have valid points and concerns and it's well put together).

    I have a lot of experience with a lot of solutions in a lot of different scenarios. A new solution to a current one can always feel better, because it excells in the painpoints the older solution had, those are the problems we're trying to address with the change, but that doesn't necessarly mean that the old solution wasn't performing better on other areas. For me CSS-in-JS (with optional static extraction and/or 0 runtime) is the perfect way, because it addresses many different issues I had through the years the most.

    There are solutions that can static extract with 0 runtime and they are able to handle dynamic props using CSS variables, so at the end it's 0 runtime and only class names. I see you mentioned this at the end also, but there are some misconceptions here. The mentioned "problems" are not necessarly unique to 0 runtime libraries, there are solution which aren't suffering from those pain points.


    The Bad

    1. CSS-in-JS adds runtime overhead: there are many compile time options that have 0 runtime using multiple different kinds of static extraction strategies.
    2. CSS-in-JS increases your bundle size: Just like the above.
    3. CSS-in-JS clutters the React DevTools. Static extracted styles will replace your styles with classNames and/or CSS variables. No clutter (not necessarily true to all solutions).

    The Ugly

    1. Frequently inserting CSS rules forces the browser to do a lot of extra work: static extraction solves this problem.
    2. With CSS-in-JS, there's a lot more that can go wrong, especially when using SSR and/or component libraries: statically extracted, nothing can really go wrong, becuase there is nothing being done in JS.

    Performance

    No JS involved, I don't want to go into it more deeply than that.

    Styles are still inserted when a component mounts for the first time... Not necessarily true. It's really based on the extraction strategy you choose. You can choose to extract everything in a single file. You will end up with the same "problem" using CSS Modules if you don't static extract into a single file btw.

    Inline styles are known to cause suboptimal performance when applied to many elements... They can, but it's nothing you should worry about, especially that it's just CSS variables. The overhead of loading an external stylesheet and handling dynamic cases with code (conditions, className concat, etc) will actually perform worse by adding more overhead imo.


    BEM

    I'm incredibly against it. 1 HUGE benefit of CSS-in-JS (inline) is that you no longer need to think of naming and organizing your selectors/styles. Best conventions are the ones that are not needed ;) Naming things, especially using BEM is a huge overhead during development, it significantly interrupts your flow.

    CSS Modules

    • Separate Stylesheets per module still need to be loaded dynamically if you want to bundle split. If you extract per component: extra request + extra style node; if you bundle: extra js size, runtime insert, extra style node.
    • Extract into 1 stylesheet: essentially same as a 0 runtime, static extracted CSS-in-JS with the overhead of maintaining styles separately with a separate system and/or language, with a separate config system (often duplicated to access values in js also) for no extra benefit/reason.
    • Naming stuff shouldn't be necessary. Less convention ftw!
    • "Separation of concerns" is what they say when putting CSS in a separate file. My personal take on this, they are right but apply it wrong. Both JSX and your styles are the same concern: view. Having deeply close together helps you understand and see immediately what your layout does, how it works, how it looks, just by looking there at the code. Separate them will cause slowdowns during development, you need to jump through several files just to check what is style.container actually is. Having them inline during development is a huge productivity boost for me.

    CSS-in-JS with runtime

    I'm not against runtimes tbh. I do have a fairly large social site with millions of contents and several multimedia types and complex user features. The complete client side codebase is 25MB in total bundle size (no gzip), yet my initial JS for a single page is 200k (no gzip), which is mostly React, it uses Styled-Components with CCSS and uses a custom, single file SSR pipeline (no Next or whatsoever) with an isomorphic codebase. I have my Lighthouse scores at 96-100 points. Could be faster w/o runtime? Yes. Would my users notice anything from it? Not really. So why would I choke myself by using a less productive/comfortable solution? :)

    Extract on your own

    It's not a too hard thing to write a Babel plugin that extracts the static parts of your css prop however you want. By that you save a lot of time spent on refactoring to a new solution on a large codebase.

    • Ken aka Frosty
      Ken aka FrostyOct 21, 2022

      This is the most thought-through piece of writing on this entire page. Everything you wrote about SCREAMS "this developer is experienced AND practical and makes great points". Something felt slightly off about OP's post (like saying "inline styles are suboptimal for performance" but...citation needed??)

      You should post an op-ed to this with the contents of this comment. It's a worthy article in itself. You have actual use-case-level information on how it doesn't even affect performance in any way a user would ever notice. That's a very valuable contribution to this topic and the entire community would be better for it.

      Either way, thanks for the very well-constructed comment. It was very helpful in adding balance to this conversation.

    • Adrien Denat
      Adrien DenatOct 23, 2022

      The performances issue was always a technological limitation but the DX of CSS-in-JS is the best to handle CSS in a codebase imo. Pre-compiled styles are solving this and it will keep on improving!
      Now the reason this is better than Tailwind is that you do not need to learn anything new. As long as you know CSS you don't need a documentation. With Tailwind you're constantly juggling between the doc and your code (ok now we have auto-completion) and there is a learning curve. CSS-in-JS has a learning curve close to zero.

    • Pieter Vincent
      Pieter VincentMar 11, 2023

      I do have a fairly large social site with millions of contents and several multimedia types and complex user features

      Can we get a link to that site? If that wouldn't work, a performance recording. It's peanuts to tell on a flamechart whether a site does or does not spend a significant amount of time on it. I'm not suggesting it doesn't have reasonable performance, I'm just interested to see how it can be implemented performantly.

      What OP mentions is not uncommon and often is even worse than a 2X difference. Although it's hard to attribute because generally it means nobody really checks performance anyway on sites that use it, so there could be other issues. But the point is also a bit that it aggravates existing problems and can easily push you beyond the performance budget.

      You seem to be concerned only about bundle size, but it's not the main issue. As the author indicated, it makes components much slower to render, because it needs to walk through your theme's complexity, over and over again. You're creating a runtime representation of your styles for React to apply to the DOM. And React is written to handle the fact that things can change. But the vast majority of your styles doesn't change after the page is loaded. Your pumping significantly more data into React and get nothing out of it you couldn't do with a static stylesheet.

      Another face of this problem is that you can't update your theme without rerendering most components, which can get very expensive

      Here's an example of the kind of mayhem it can cause on Reddit, where switching a theme takes over 100ms of scripting alone.

      Image description

    • Oziel Perez
      Oziel PerezJun 8, 2023

      I have way too little time to be going over how to construct an entire system that fixes all of these things, which is why my vote goes to eliminating CSS-in-JS. Wish React would instead do Single File Components so that people can start learning to separate concerns.

  • stefanvonderkrone
    stefanvonderkroneOct 19, 2022

    what about avoiding dynamic css in components, so you use the css as you would with CSS/SCSS-modules? As for my understanding, shouldn't the browser recalculate all dom-nodes with the css-modules as well as soon as they are injected in the dom? So, if I avoid dynamic css-classes in my CSS-in-JS I could prevent some issues, if I recall correctly.
    I'm thinking here of a quite large code-base that uses styled-components. We are aware of some performance issues, but it would be quite time-consuming to ditch styled-components and to refactor everything in favor of (S)CSS-modules.

  • Taha Hassan Mujaddadi
    Taha Hassan MujaddadiOct 19, 2022

    I never liked writing CSS in the JS. For us CSS Modules is enough. We just concatenate the component name with the CSS classes by using Webpack to make them locally scopped. We have tried BEM but it cumbersome to come up with all the names.

  • Dan Jones
    Dan JonesOct 19, 2022

    What I like about vue components is that they provide the first two benefits of CSS automatically.

    <template>
      <TemplateCodeHere/>
    </template>
    
    <script>
      // js code goes here
    </script>
    
    <style lang="scss" scoped>
    .class {
        .child {
            /* some styles for this component */
        }
    }
    </style>
    
    Enter fullscreen mode Exit fullscreen mode

    Now, I've got SCSS. The source is located in the same file as the component. It's compiled to locally-scoped regular CSS.

    I don't get the benefits of javascript variables in the CSS, but there have only been a few rare occasions when I've found that beneficial. Usually, I can get away with doing something like this:

    <template>
        <section :class="computedClass">
            <!-- more stuff -->
        </section>
    </template>
    
    <script>
    export default {
        setup() {
            const computedClass = computed(() => someCondition ? 'class-a' : 'class-b');
    
            return { computedClass };
        }
    }
    </script>
    
    <style lang="scss" scoped>
    section {
        &.class-a {
            /* some styles here */
        }
        &.class-b
            /* different styles here */
        }
    }
    </style>
    
    Enter fullscreen mode Exit fullscreen mode

    So, I use Javascript variables for determining the class, and that's usually good enough.

    • Lars Rye Jeppesen
      Lars Rye JeppesenOct 20, 2022

      Angular does the exact same. But you can also have sass in separate file. Up to you

  • Dmitry Utkin
    Dmitry UtkinOct 19, 2022

    Unpopular opinion(maybe), but nothing beats Emotion in DX. Fixing perf issues before you even have them is a bad idea, imho. And it's not about the overall performance, it's just one of the bits that might(!) make you app\website feel slow on some(!) devices. Just turn on the CPU throttling while testing and have a (somewhat) real grasp about what are you fighting for. And then compare it to the Network throttling. I guarantee you a surprise :)

  • András Tóth
    András TóthOct 19, 2022

    Finally! Finally, oh gosh, finally! I have felt that CSS-in-JS was an organizational mistake solved by a sub-optimal hack that goes against all browser optimization features. You might ask what organizational mistake?, and my answer is that people who don't have in-depth CSS knowledge needing to write frontend code (similarly to people who don't have SQL skills need to write backend), while there is also no time, process or structure to avoid and discuss (potential) CSS conflicts.

    CSS is an exception oriented language, which requires people to agree on the behavior and constraints of the main layout elements and main components and then write minimal set of exceptions to deviate from those defaults. (CSS layers as a feature is also on the horizon, helping with the modularization of frameworks/large components, etc.).

    In-depth reading by somebody much, much better than me: every-layout.dev/rudiments/global-...

    The colocation was already solved in 2016 (or even earlier) - I clearly remember importing scss files straight to angular 1.x components.

    The variables would have been mostly solved by smart utility classes and conditionally adding those - but nowadays you have CSS variables.

    • Rense Bakker
      Rense BakkerOct 20, 2022

      You can't change CSS variables at runtime, except by doing DOM manipulation, which is ugly and nobody will know what is going on. This is not a problem in css-in-js.

  • Luis Carlos Limas
    Luis Carlos LimasOct 19, 2022

    First time I read some of your posts, great article!

    Just referring to your list of "The Goods", I strongly suggest you to look at Dojo project. Is not popular at all... But, I worked with it by years and loved it. I can easily say it's the best JS framework I've ever worked with.

    Currently I'm an Angular guy, (I refused React by just scratching its surface... my stance with it can be represented by this meme).

    In case you don't know Dojo, it's supported by the OpenJS Foundation and it has some really bright minds behind its design. In the past years they have been doing a reimagining Typescript version of it (Dojo 2). For big projects I think it's the best bet as it's a highly structured and maintainable... That's IMO, just thought you should know about it as peer by reading your post I think you would love it too :)

    • Lars Rye Jeppesen
      Lars Rye JeppesenOct 20, 2022

      Problem is that once you use modern Angular with observables etc, it's hard to go to horrible hooks and promise based frameworks.

      • Luis Carlos Limas
        Luis Carlos LimasOct 21, 2022

        Well you're in part right as RxJS has popularized a lot reactive programming. However, one of its main features, Observables, are part of ES8 which Dojo 2 provides :)

        • Lars Rye Jeppesen
          Lars Rye JeppesenOct 21, 2022

          yeah, I am hoping Observables gain some traction, I thought it had been dropped from the specs but if they are coming it will be a good thing. cheers

          • Joel
            JoelOct 26, 2022

            @luiscla27 Can you point to somewhere that indicates Observables will be part of ECMAScript?

  • Michael Lynch
    Michael LynchOct 19, 2022

    These days, I personally use CSS modules with plain, vanilla CSS, though I used to use both LESS and SASS years ago and can see the advantages they bring to the table - the most valuable to me was nesting, though, as you noted, CSS should have that soon and variables, which was solved with custom properties (we just need them available in media queries!).

    Ultimately I'd rather not depend on any tooling to write CSS as there's no setup / overhead, performance issues or learning curve, beyond learning the language itself, that is. I also just don't find any of these libraries or packages solving any particular problems I have (modules solved scope, which is really the only big issue I've faced over the years). I use BEM as a convention and I believe that if you can name a JS function, you can also name a CSS class. Naming just doesn't seem as difficult to me as some people make it sound, though I do appreciate its importance. Obviously everyone has their own preferences though.

    Anyway, that was an excellent article. Your analysis of tradeoffs provides a very helpful overview of CSS-in-JS and the benchmarks you mentioned are very interesting to hear. Thanks for sharing.

  • Jake Lane
    Jake LaneOct 20, 2022

    Hey lead on the Compiled CSS-in-JS project here 👋. Just thought I'd clear up some aspects of the Compiled library and our future roadmap.

    For the components that are included at runtime, those are intended for use in development only. For production use, we're going to recommend people use stylesheet extraction which moves all the CSS into an atomic stylesheet. That'll mean that all the runtime react components will be stripped out. We haven't fully made this clear in documentation yet as we're still ironing out some issues with dev experience.

    The dynamic prop support is intended as a migration feature to allow people to adopt compiled without fully re-writing their code. We required this as part of our migration journey at Atlassian. It does end up with non-optimal performance this way so we're we'll be building more tooling around writing fully static CSS.

    At Atlassian, we did a deep dive into whether to use CSS modules or continue with CSS-in-JS. We did end up selecting Compiled as our best path forward for a few reasons, but to simplify:

    • Devs are familiar with CSS-in-JS and we can migrate without too much difficulty to a build time CSS-in-JS library. It didn't seem like a good use of time to re-write all our CSS.
    • We find CSS-in-JS works really well for our design system and the amount of CSS we have at Atlassian. We'd have a lot of new challenges around ensuring code quality, encapsulation, and reusability without it.
    • We can end up with the ~same performance characteristics of CSS modules with Compiled on the CSS prop - possibly better with tooling to make better use of atomic CSS.
  • Punit Sethi
    Punit SethiOct 20, 2022

    Thanks for this insightful post.

    One Q - why not try to move towards zero-runtime CSS-in-JS? Like what linaria, goober, vanilla-extract does?

    It lets us have colocation, dynamic styling, scoping while getting rid of the runtime performance overhead.

    Am I missing something?

  • Eric Burel
    Eric BurelOct 20, 2022

    "With Sass Modules, you lose benefit 3 of CSS-in-JS (the ability to use JavaScript variables in styles)"

    In React you can do style={{"--foobar": props.color }} to define a CSS variable.
    Ironically, I've learnt this pattern from Josh Comeau Styled Component article joshwcomeau.com/css/styled-compone...
    Actually you also mention this pattern at the very end when talking about compiled CSS, as in this example: compiledcssinjs.com/#speed-up-your...

    Of course, you can do the same for "boolean" props, using classNames.
    I've noticed a performance penalty when using classNames vs injecting props in styled-components: github.com/styled-components/style...
    Which is also a bit ironical: styled components are faster when injecting props than when using an actual idiomatic CSS approach

    I think this has been mentioned in other comments but I just wanted to share the precise patterns.
    Those patterns seems to have performance penalty, and I am not sure why, because they are technically correct: maybe that's the issue we should address?

    (edited)

  • Nick McCurdy
    Nick McCurdyOct 20, 2022

    CSS-in-JS clutters the React DevTools.

    This isn't relevant, you can just ignore Emotion* in DevTools.

  • iMoses
    iMosesOct 20, 2022

    Have you considered that your issues are not with css-in-js but specifically with emotion?
    Take linaria for example. They work in build time instead of runtime, so about half your complaints are instantly irrelevant...

  • Andreas Opferkuch
    Andreas OpferkuchOct 20, 2022

    While I agree with the general direction of moving to separately generated CSS files, a lot of this still sounds like throwing the baby out with the bath water to me.

    The fact that one should declare styles that aren't dynamic outside of components has been mentioned again and again over the years. Not being aware of this is less of a problem with the technology but with engineers having a lack of understanding how the technologies that they use work. I believe that this particular concept is pretty simple to understand and apply appropriately. (Also keeping premature optimization in mind.)

    And so I think it is unfortunate that you didn't take a few minutes to refactor the existing code and add another column to the benchmark table.

    (Utility classes) It's not the end of the world, but it's definitely less convenient.

    Have to say - memorizing abbreviations or keeping a table of them open at all times doesn't seem convenient to me. That's why I have always avoided companies that use tailwind. 😅
    Personally, I find typing native CSS rules way more convenient, especially since IDEs have had autocomplete since forever by now.

    Speaking about autocomplete... What about autocomplete (and type safety) for design tokens? I don't see that mentioned, yet I think it's a pretty great perk for larger code bases. Even the compile-time libraries you briefly touched on can offer that.

    And speaking about compile-time libraries... vanilla extract seems more promising to me then compiled, so I think it's a shame that you focused on that and sort of lumped these libraries together (I think it's easy to miss that the listed arguments are about compiled).

    For these reasons, I will probably use vanilla extract instead of sass modules for my next project and hope some other people will give it a closer look as well. 🙂

  • Ajinkya Borade
    Ajinkya BoradeOct 20, 2022

    I think this article will get tailwind more users <3

  • Silvestar Bistrović
    Silvestar BistrovićOct 20, 2022

    I had so many comments, that I had to publish a blog post about this topic.

    silvestar.codes/articles/why-i-nev...

  • Pål Nes
    Pål NesOct 20, 2022

    Instead of SASS, one could use PostCSS

  • Toby Brancher
    Toby BrancherOct 20, 2022

    I appreciate the time and effort in the post, but just wanted to be vocal in saying I disagree - just in-case there are others that are scrolling through the comments.

    I should also immediately add that the performance and dev issues highlighted in the post have solutions for them. Static extraction, dev configuration.

    For me, the semantic quality, separation of concerns and devex are what are important to me with styling and css-in-js currently provides the best solution for this.

    • CSS-in-JS is semantic by nature and componentisation can happen easily on two levels, either with modularised components, or with modularised blocks of CSS and values
    • The styles I write have a direct connection with the markup, having these next to each other is important for speed and flexibility. These styles can be made up of componentised CSS also, but that is easily and semantically located based on what it is.
    • I build a lot of things that need to be bespoke and beautiful, and it's important to me that the devex for being creative, efficient or debugging is rapid, using the emotion babel plugin is kind of, out of this world for speed of debugging.

    I think it's really important to understand the values and principals you may subconsciously use when selecting one of the many styling solutions in the mix currently. And remember we also have different values & principals and are building different things. What might be a great solution for one project, might not be such a great one for another, and picking a general approach to all will depend on what you are looking to build in general. 🙌

    You are not quite the second most active maintainer of emotion with your 24 commits btw! Not trying to discredit you, but the opening line makes it sound like you are more involved with the codebase than you make out: github.com/emotion-js/emotion/comm...

    • Søren Høyer
      Søren HøyerOct 20, 2022

      Haha good catch with the 24 commits. :D

  • Andrew Stacy
    Andrew StacyOct 20, 2022

    Very interesting article. Thank you for writing it.

    To all devs looking for alternatives, look into web components and shadow DOM. A lot of these problems have been solved in the platform already.

  • Søren Høyer
    Søren HøyerOct 20, 2022

    As others have mentioned you forgot to mention PostCSS.
    Also, you only do have 24 commits but make it sound like much more than it is...

    Edit: Airbnb recently moved from SASS to Linaria and wrote a great article about it. I recommend people reading this one also have a look at medium.com/airbnb-engineering/airb... - judging from that, Linaria might just be the best pick.

  • Lars Rye Jeppesen
    Lars Rye JeppesenOct 20, 2022

    After using Tailwind for 6+ months now, I can say I have never had such fun doing css. Tailwind is just so nice

  • Lars Rye Jeppesen
    Lars Rye JeppesenOct 20, 2022

    Angular as well, it's just much nicer to work with.

    Don't even get me started with the awful react hooks.

  • Allan Bonadio
    Allan BonadioOct 21, 2022

    The good:
    1 Locally-scoped styles:
    Solution: Use hierarchical cascading styles, the way CSS was designed to be used.

    .WaveView { position: relative; }
    .WaveView .row { position: absolute; }
    
    Enter fullscreen mode Exit fullscreen mode

    .row will never affect anything that isn't inside of a .WaveView ; then you can reuse .row somewhere else, which is similarly contained inside another element. Even something that has nothing to do with position. Each element creates a new namespace for classes that you can use.

    I highly recommend Scss, Sass, Less, Stylus or similar packages that allow you to write like this:

    .WaveView {
        position: relative;
        .GLView {
            position: absolute;
        }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    Now, you know Exactly what affects what; you've got local scopes staring you in the face. Added bonus: you can see which div is which in the debugger, without guessing what the hashed names are. No 'modules' files wandering around are needed. No funny junk is being inserted into your HTML that you don't know about.

    For BIG apps, you can add levels to the hierarchy. Each level adds another unique namespace. You will never run out.

    .TrainPage {
        .NorthPanel {
            .WaveView {...
                .GLView {...
    
    Enter fullscreen mode Exit fullscreen mode

    If some bonehead put in a global .row class, take it out immediately, otherwise everybody else who uses .row will base their CSS on it, and it'll be harder to remove later. Or, Better, get the programmer who put it in to remove it and fix all resulting glitches. See also DELETE FROM mytable in SQL.

    2 Colocation:
    Put your .css (or .scss or whatever) files right next to your .js files. You don't need to separate them in different directories; in fact, don't. That's what the suffix is for, so there's no conflict! Neither will lose the other. Also put the .spec.js file right next to those two, so you don't forget about it.

    3 You can use JavaScript variables in styles
    You can ALREADY do this in react:

    <p style={{
        color: colors.primary,
        fontSize,
        border: `1px solid ${colors.border}`,
    }}>
    
    Enter fullscreen mode Exit fullscreen mode

    LEARN THE TOOLS YOU ALREADY KNOW, the ones already taking up code space in your app's footprint, and brain space in your head.

    The Neutral: 1. It's the hot new technology.
    NEVER glom on yet another package for this reason! Only incorporate a new package because you really need it. And, because it adds significant new capabilities; if it's simple, just write it yourself so you have more control over it. I've written too much code to get around misfeatures in packages that I could have just written myself the way I want it.

    I see packages all over that do exactly what you can do if you just use the right control panel options, or the right HTML or React features. Read the docs for HTML, CSS, JS, React, etc; often it's already built in. (Otherwise, you'll have to read the docs for the package you're adding on anyway, right?) HTML5, CSS3 and ES10 have awesome new features; learn them now before you write more code, painfully, without them.

    Every time you add on somebody else's software, you run the risk that it'll introduce another bug somewhere, and you won't notice it until you've installed a dozen more packages, so you won't have a clue where the problem is. Better to let the OS people or the HTML people or the React people or whomever incorporate the feature - they know the HTML code & React code best, and they'll make sure it still works next major release.

    • Baptiste Firket
      Baptiste FirketOct 21, 2022

      So, following your logic, nobody should use JS framework either and use vanilla JavaScript.

      I think the assumption that those CSS-in-JS library are so popular because "it's hot and new technology" is wrong. They are popular because they make developer life easier. Of course there are ways to achieve the same without them. But it's easier and faster. I don't want to use BEM, I don't want write and concatenate classNames, I don't want to use object style css like in your 3rd example. I do want to be able to access my component props in my css out of the box, etc.

      The problem is that most of developer (like me) are not aware of the performance cost (appart from bundle size).. So big thanks to the author of this article to highlight it, and I personally think that a solution like Linaria is very promising.

      • Allan Bonadio
        Allan BonadioOct 21, 2022

        "So, following your logic, nobody should use JS framework either and use vanilla JavaScript."

        no, that's not what I said.

        Let's say you need to recognize and parse postal codes, US, Mexican and Canadian. So you find a package that does ALL postal codes in the world. It's OK at first, but you run into complications. User types in a postal code with a mistake - and suddenly the package decides that it's a Turkish postal code, so it doesn't flag an error. You have to pass weird options to get it to stop, using Intl.* stuff. And, you had to futz with it for a while to figure out which options. Meanwhile, it doesn't do the "+4" part of US zip codes. And, there's other problems - there's 200 countries in the world!

        So the issue is, the package has features that you don't need, and they get in the way. In that case, check and see if what you want is easy to do by hand. In this case, you can write a few regexes, and a few IF statements, and it works exactly the way you want. That's the way to do it.

        Now, maybe you could find a package that parses US, Mexican and Canadian postal codes. Looks good. But, you still have to read the docs - somebody else invented it, not you. And, upgrade to newer versions. And, run the risk that they have bugs, and you don't know how to fix them. Then you find a fix, but you can't change the package itself, so you have to add your own code to work around the bug. Then, they come up with a new version, and you wonder if they fixed that bug, or if the new version doesn't work with your fix, so...

        This might sound contrived to you, but I've had each one of the above problems happen to me before. Once I made a script that would go in and hack the package; it would run every time I installed it.

        One time, I had this really, really weird bug. A jQuery had a jQ function 'format'. Worked for a while, but then I tracked down a bug to the fact that the 'format' function was gone. Just not there. I checked out the package, downloaded again, 'format' is there, everything was fine till the software ran, and the format function was gone. I finally figured out that this other jQ package also had had a function named 'format', but not in the version I was using. So, the new version got rid of it. Literally. It deleted anything named 'format, even if it wasn't their format function. I wasted so much time on that.

        That's what I'm talking about when I say, each package you use is a small liability, and if it just does something simple, it might be better to write it yourself.

  • Hamzat Victor Oluwabori
    Hamzat Victor OluwaboriOct 21, 2022

    Try benchmarking with Stitches.js

  • James Moss
    James MossOct 21, 2022

    We've been using CSS Modules for a few years now and haven't really run into any issues.
    One thing I would raise though, we did initially start using SASS Modules as well like you're proposing here but quickly ran into slow compile times as we approached 3000+ components. We swapped out SASS for just PostCSS plus a few plugins (nested css selectors, custom media) and CSS build times increased by around 5-6x. I would highly recommend ditching SASS as well.

  • Ken aka Frosty
    Ken aka FrostyOct 21, 2022

    Less than 20 commits compared to Andarist and mitchellhamilton's OVER 100 a piece -
    Don't take my word for it, just look yourself:

    The actual two top maintainers-
    github.com/emotion-js/emotion/comm...
    github.com/emotion-js/emotion/comm...

    The author of this article who has substantially fewer commits than those actual top two:
    github.com/emotion-js/emotion/comm...

    It sort of begs the question, why would you start an article very loudly saying "the 2nd most active maintainer of Emotion" when that is categorically, provably false?

    Very much questions the intent and legitimacy of the rest of the entire article, sadly. Especially when the article ends in a promotion for the organization you now work for?

    Must be complete coincidence, surely.

    • Marko A
      Marko AOct 24, 2022

      "active maintainer" doesn't mean one with most contributions, it means one that is actively working on the project, hence the word "active".

      If I check your second link with "mitchellhamilton" as the contributor, I can see that their last commit is over 6 months ago and commits before that are highly irregular intervals spanning months between commits. Not really "active" day to day. "Andarist" on the other hand has way more recent commits and really is active.

      Although I do agree saying "2nd most active maintainer of Emotion" was unnecessary. They could have just mentioned "one of active contributors to Emotion" without specifying "position".

  • Yoshiki Ohshima
    Yoshiki OhshimaOct 21, 2022

    I am a bit surprised by the benchmark results. It measured the time to render 20 (not 200 or 2000) items of names and emails and status, and it came out in the range of 50ms and 25ms range for one rendering cycle? I think the browser DOM and JavaScript is not that slow, and an implementation in Vanilla JS would be much faster than that. I am curious where the time actually goes.

  • JW
    JWOct 22, 2022

    If huge components need huge stylesheet, lazy load and lazy insert styles rules looks like make better performance.

  • tnypxl
    tnypxlOct 24, 2022

    CSS-in-JS allows developers to skip the ceremony of learning how to write lean, well-structured markup and CSS. It allows them to be indifferent towards the DOM bloat their sloppily and needlessly nested components are creating.

    Creating an approach that repackages problems at a different layer in the stack in the name of developer experience is not always ideal. CSS-in-JS is one those approaches and its time we revisit our motivations for using it. The author did a commendable job of using evidence and data to reach their conclusion.

  • Carl-Erik Kopseng
    Carl-Erik KopsengOct 25, 2022

    When you say "Custom HTML", does that mean using Web Components? The term is a bit ambiguous. To make things clear, you now do

    <my-foo>yo</my-foo>
    
    Enter fullscreen mode Exit fullscreen mode

    and in the implementation of my-foo utilize utility classes (like .align-center)?

  • Vadorequest
    VadorequestOct 25, 2022

    I'm aware of the perf issues with Emotion, and still chose it because I didn't find a better alternative to get dynamic CSS based on runtime variables coming from an API.

  • Jasperrr91
    Jasperrr91Oct 25, 2022

    Interesting bits of code to browse through and it certainly has its charm. I would however never implement such code in companies I work at. It's far harder to read than simple components in . Making it harder to maintain since with the current job market there's mainly juniors and medior developers joining companies. The main key is to keep your code as simple and maintainable as possible.

  • Lucian I. Last
    Lucian I. LastOct 27, 2022

    Here’s a quick converter I built

    lil5.github.io/jss-to-scss/

  • xcmk123
    xcmk123Oct 29, 2022

    @jfbrennan your demo github.com/jfbrennan/m- is really awesome. Is there any way to use that on ReactJS project ?

  • Nitipit Nontasuwan
    Nitipit NontasuwanNov 4, 2022

    I think CSS-in-JS is not a problem here, It's about how you use CSS-in-JS with React.

  • Stephen Haberman
    Stephen HabermanNov 15, 2022

    @srmagura curious if you've published the benchmark code? Would be fun to play around with. Thanks!

  • Hamza Hamidi
    Hamza HamidiNov 17, 2022

    Styled-components are a major source of performance issues with re-renders and Js execution time especially in mobile devices. If your code is SEO vital, styled components is going to harm your Core Web Vitals. You are much better off with CSS Modules.

    • Hamza Hamidi
      Hamza HamidiNov 17, 2022

      The idea was doomed from the start, JS performance on mobile devices is very poor especially on Android devices. You're better off by using vanilla css modules. Don't forget that Google crawlers use the mobile view to benchmark your website! Not surprising since 70% of web traffic are from mobile devices, and this number will continue to grow.

  • zicjin
    zicjinJan 24, 2023

    I understand the importance of performance, but the development efficiency is also very important (especially toB projects). Is there any rich UI library based on scss besides the old bootstrap? Almost every sleek, beautiful new UI library I see is built with css-in-js libraries.

  • Aditya Tyagi
    Aditya TyagiApr 20, 2023

    Came here from Shifting Away From CSS-in-JS With Sam Magura - RRU 211 (react round up). If anyone here hasn't, please check it out: topenddevs.com/podcasts/react-roun...

  • 𒎏Wii 🏳️‍⚧️
    𒎏Wii 🏳️‍⚧️May 8, 2023

    and your styles will apply globally regardless of where the .css file is located

    This can be somewhat mitigated by having the JS link the corresponding CSS file like so:

    // The made-up function that does the magic:
    linkStyles('/path/to/styles.css', { scope: 'my-component' })
    // Defining the component in some way:
    class MyComponent { /* ... */ }
    
    Enter fullscreen mode Exit fullscreen mode

    This would allow putting the CSS in a separate file, either in a parallel tree or next to the JS file, lets server-side tools include preloading tags in the resulting CSS, allows the browser to fetch and cache styling separately from the JS, and otherwise comes with most of the same maintainability advantages as CSS-in-JS.

  • Nerdy Deeds, LLC
    Nerdy Deeds, LLCJun 19, 2023

    The pushback OP is experiencing here honestly baffles me. Frankly, so does that amount of struggle folk seem to be having with the simple act of being expected to scope your bloody stylesheets! This is something I expect even my JD's to be proficient in within 2 weeks of their first hire! Hell, we have INTERNS here that have no troubles with the concept after a 90 second explanation!

    There are literally DOZENS of ways to address this, many (most?) that require a minuscule amount of thought: so much so that it's wholly possible to establish up-front, with the inclusion of a couple lines of React code and a SINGLE line of Sass code per page/view or component!

    My ghast is well and truly flabbered that, for what certainly appears to have been a solved issue for years now, there's this much controversy surrounding it, based almost exclusively, based on the comments here, at least, on the arguments, "but it's nominally-less convenient!" and "but we don't WANNA!".

    The ability to use JS variables inside of CSS was, briefly, moderately handy (although in the end, it's still transpiled down to CSS! Meaning that there's an extremely finite number of use-cases not already solved by CSS itself - or certainly by Sass for the remainder - nowadays).

    But guys? It. Is. Not. Performant!

    And this is coming from an engineer from one of the TOP TWO AUTHORS OF THE PRODUCT HE IS DESCRIBING!

    Now, maybe it's because I'm old, and remember having to optimize down to the BYTE for load-, transfer-, and render-times (hell, I worked a web project where we optimized for the distance the drive head had to move on the disk!), but, to me, a 50-200% savings in load time simply by moving the same code to a different file (which is INTENDED for the purpose), and following basic conventions (which, a damn linter can handle for you!) is a bloody no-brainer non-starter. The problem is NOT that your server/users' computers aren't powerful enough. It's that you're being LAZY. "I don't wanna" is not a valid excuse for sloppy coding or anti-patterns!

    Sorry! Learn the language and get over having everything auto-scripted for you! Otherwise, I'm certain there's a GPT out there that CAN handle the additional complexity of ONE line of code.

  • Gustavo Magnago
    Gustavo MagnagoJul 8, 2023

    Idk, for me even after this I will keep Emotion, it’s just easier to code, cleaner and, specially for a high ADHD person as I, it gets beautifully organised with colocation, making it way easier to maintain and read through the years working at the same project. Theming is my passion ❤️

  • Alban X
    Alban XJul 9, 2023

    Who ever invented or uses CSS-in-JS deserves a special place in hell

  • Hardik Shah
    Hardik ShahAug 29, 2023

    Hello @srmagura - this post is amazing and covers several aspects. Thank you for taking time to explain the pain-points and discussing several view points.

    A question for @srmagura and others knowledgeable -

    I am using EmotionJS in my React component library wherein every component has it's corresponding EmotionJS styles, how can I migrate my EmotionJS styles to SASS style file (i.e. .scss) in an automated fashion if I want to break away with EmotionJS?

    This is precisely the topic of this article but I didn't see any concrete plan for automated migration.

  • Nitipit Nontasuwan
    Nitipit NontasuwanJan 20, 2024

    Hi, I'm from 2024 :)
    CSS-in-JS is still the future ... with many features it can achieve.

    1. Component Styling in OOP manner. keenlycode.github.io/adapter/guide...
    2. Style under Shadow DOM just work with adoptedStyleSheet keenlycode.github.io/adapter/guide...
    3. Element Scoped Style with nesting rules. keenlycode.github.io/adapter/guide...
    4. Style Filtering keenlycode.github.io/adapter/guide...
    5. Web Assembly for fast parser : lightningcss.dev/

    If we use cssStyleSheet, then CSS can be and should be parsed just once per page, not per component or per render.

    The downside maybe it less compatible with old browsers (~90% compatible). However, it's possible to fixed it with polyfills or JS Build Tools to target for older browsers.

    and much more...

    If you're curious , you can visit Adapter

Add comment