DEV Community

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

Sam Magura on October 16, 2022

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...
Collapse
 
airtonix profile image
Zenobius Jiricek • Edited

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.

Collapse
 
dstaver profile image
Daniel Staver

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.

Collapse
 
markohologram profile image
Marko A • Edited

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.

Collapse
 
baukereg profile image
Bauke Regnerus • Edited

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.

Collapse
 
airtonix profile image
Zenobius Jiricek

There's linting you can use to enforce this.

Collapse
 
eballeste profile image
Enrique Ballesté Peralta • Edited

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.

Collapse
 
airtonix profile image
Zenobius Jiricek

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.

Thread Thread
 
markohologram profile image
Marko A

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".

Thread Thread
 
eballeste profile image
Enrique Ballesté Peralta • Edited

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

Thread Thread
 
microcipcip profile image
Salvatore Tedde

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.

Thread Thread
 
jasper91 profile image
Jasperrr91

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.

Thread Thread
 
eballeste profile image
Enrique Ballesté Peralta • Edited

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
Collapse
 
airtonix profile image
Zenobius Jiricek • Edited

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 👍️

Thread Thread
 
eballeste profile image
Enrique Ballesté Peralta • Edited

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

Collapse
 
gameboyzone profile image
Hardik Shah

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?

Collapse
 
juliansoto profile image
Julian Soto

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

Collapse
 
latobibor profile image
András Tóth

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.

Collapse
 
airtonix profile image
Zenobius Jiricek

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

Thread Thread
 
latobibor profile image
András Tóth

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

Collapse
 
ajinkyax profile image
Ajinkya Borade

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

Collapse
 
zohaib546 profile image
Zohaib Ashraf

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

Collapse
 
allanbonadio profile image
Allan Bonadio

"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.

Collapse
 
markohologram profile image
Marko A

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.

Collapse
 
leob profile image
leob

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

Collapse
 
halfist profile image
Halfist

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.

Collapse
 
wintercounter profile image
Victor Vincent • Edited

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.

Collapse
 
kenakafrosty profile image
Ken aka Frosty

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.

Collapse
 
inwerpsel profile image
Pieter Vincent • Edited

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

Collapse
 
grsmto profile image
Adrien Denat

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.

Collapse
 
ozzythegiant profile image
Oziel Perez

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.

Collapse
 
zainw profile image
Zain Wania

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

Collapse
 
srmagura profile image
Sam Magura

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.

Collapse
 
nidemos profile image
niddy - the animated atheist

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

Thread Thread
 
jonaskuske profile image
Jonas

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 :)

Collapse
 
spock123 profile image
Lars Rye Jeppesen

You will absolutely love Tailwind, trust me bro

Thread Thread
 
humoyun profile image
Humoyun Ahmad • Edited

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.

Thread Thread
 
spock123 profile image
Lars Rye Jeppesen • Edited

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.

Thread Thread
 
humoyun profile image
Humoyun Ahmad • Edited

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 .

Collapse
 
tomrav profile image
Tom Raviv

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.

Collapse
 
srmagura profile image
Sam Magura

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?

Collapse
 
tomrav profile image
Tom Raviv

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.

Thread Thread
 
srmagura profile image
Sam Magura

@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 :)

Thread Thread
 
tomrav profile image
Tom Raviv

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.

Collapse
 
brense profile image
Rense Bakker • Edited

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 :(

Collapse
 
iohansson profile image
iohansson

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.

Collapse
 
brense profile image
Rense Bakker

Ok, what about the other argument?

Thread Thread
 
iohansson profile image
iohansson

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.

Thread Thread
 
brense profile image
Rense Bakker

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.

Thread Thread
 
airtonix profile image
Zenobius Jiricek

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
Collapse
 
redbar0n profile image
Magne • Edited

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.

Collapse
 
dinsmoredesign profile image
Derek D

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...

Collapse
 
carlldreyer profile image
Carl Lidström Dreyer

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] {}

Collapse
 
clay profile image
Clay Ferguson

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.

Thread Thread
 
carlldreyer profile image
Carl Lidström Dreyer

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.

Thread Thread
 
spock123 profile image
Lars Rye Jeppesen

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.

Thread Thread
 
mattaningram profile image
Mattan Ingram • Edited

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.

Collapse
 
spock123 profile image
Lars Rye Jeppesen

Angular does this by default.
Very very nice

Collapse
 
ivan_jrmc profile image
Ivan Jeremic

Charka-UI sweating right now reading this article😂

Collapse
 
primos63 profile image
Primo Sabatini

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.

Collapse
 
mariomui_53 profile image
Yi Kan Mario Mui

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.

Collapse
 
srmagura profile image
Sam Magura

Hahahahah

Collapse
 
frontendtony profile image
Anthony Oyathelemhi

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)

Collapse
 
spock123 profile image
Lars Rye Jeppesen

And Tailwind is not opiniated, unlike Bootstrap.

110pct agree with all your points

Collapse
 
jakelane profile image
Jake Lane

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.
Collapse
 
teetotum profile image
Martin

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
Collapse
 
renancferro profile image
Renan Ferro

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

Collapse
 
allanbonadio profile image
Allan Bonadio

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.

Collapse
 
baptistefkt profile image
Baptiste Firket

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.

Collapse
 
allanbonadio profile image
Allan Bonadio

"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.

Collapse
 
sorenhoyer profile image
Søren Høyer • Edited

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.

Collapse
 
gothy profile image
Dmitry Utkin

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 :)

Collapse
 
leob profile image
leob

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.

Collapse
 
luiscla27 profile image
Luis Carlos Limas • Edited

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 :)

Collapse
 
spock123 profile image
Lars Rye Jeppesen

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

Collapse
 
luiscla27 profile image
Luis Carlos Limas • Edited

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 :)

Thread Thread
 
spock123 profile image
Lars Rye Jeppesen

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

Thread Thread
 
jwess profile image
Joel

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

Collapse
 
latobibor profile image
András Tóth • Edited

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.

Collapse
 
brense profile image
Rense Bakker

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.

Collapse
 
geekybiz profile image
Punit Sethi

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?

Collapse
 
7loque profile image
Toby Brancher • Edited

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...

Collapse
 
sorenhoyer profile image
Søren Høyer

Haha good catch with the 24 commits. :D

Collapse
 
kenakafrosty profile image
Ken aka Frosty

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.

Collapse
 
markohologram profile image
Marko A • Edited

"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".

Collapse
 
goodevilgenius profile image
Dan Jones

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.

Collapse
 
spock123 profile image
Lars Rye Jeppesen

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

Collapse
 
spoike profile image
Mikael Brassman

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.

Collapse
 
meduzen profile image
Mehdi M.

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.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

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.

Collapse
 
juliansoto profile image
Julian Soto

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.

Collapse
 
srmagura profile image
Sam Magura

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.

Collapse
 
lamualfa profile image
Laode Muhammad Al Fatih

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.

Collapse
 
srmagura profile image
Sam Magura

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.
 
kopseng profile image
Carl-Erik Kopseng

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)?

Collapse
 
nitipit profile image
Nitipit Nontasuwan • Edited

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

Collapse
 
zicjin profile image
zicjin • Edited

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.

Collapse
 
spock123 profile image
Lars Rye Jeppesen

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

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

Collapse
 
ericburel profile image
Eric Burel • Edited

"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)

Collapse
 
nerdydeedsllc profile image
Nerdy Deeds, LLC

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.

Collapse
 
gustavo_magnago_b5430810e profile image
Gustavo Magnago

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 ❤️

Collapse
 
adityatyagi profile image
Aditya Tyagi

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...

Collapse
 
apperside profile image
Apperside

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!

Collapse
 
hamzahamidi profile image
Hamza Hamidi

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.

Collapse
 
hamzahamidi profile image
Hamza Hamidi

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.

Collapse
 
tnypxl profile image
tnypxl

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.

Collapse
 
jamesmoss profile image
James Moss

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.

Collapse
 
imoses profile image
iMoses

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...

Collapse
 
shadow profile image
Andreas Opferkuch • Edited

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. 🙂

Collapse
 
mujaddadi profile image
Taha Hassan Mujaddadi

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.

Collapse
 
bentechcoder profile image
Benjamin Lebron

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

Collapse
 
idad5 profile image
Markus Mathieu

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.

Collapse
 
codebytesfl profile image
codebytesfl

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.

Collapse
 
lewiscowles1986 profile image
Lewis Cowles

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

Collapse
 
baptistefkt profile image
Baptiste Firket

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.

Collapse
 
stefanvonderkrone profile image
stefanvonderkrone

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.

Collapse
 
michaelynch profile image
Michael Lynch

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.

Collapse
 
stojakovic99 profile image
Nikola Stojaković

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.

Collapse
 
gameboyzone profile image
Hardik Shah

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.

Collapse
 
elsyng profile image
Ellis

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)

Collapse
 
pavellaptev profile image
Pavel Laptev • Edited

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.

Collapse
 
fredx profile image
Gianluca Frediani

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)

Collapse
 
yoshikiohshima profile image
Yoshiki Ohshima

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.

 
jasper91 profile image
Jasperrr91

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.

Collapse
 
codevictor profile image
Hamzat Victor Oluwabori

Try benchmarking with Stitches.js

Collapse
 
nickmccurdy profile image
Nick McCurdy

CSS-in-JS clutters the React DevTools.

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

Collapse
 
luisbraga profile image
Luís Braga • Edited

How about Linaria?

Collapse
 
jiangweixian profile image
JW

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

Collapse
 
lil5 profile image
Lucian I. Last

Here’s a quick converter I built

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

Collapse
 
nitipit profile image
Nitipit Nontasuwan

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

Collapse
 
spock123 profile image
Lars Rye Jeppesen

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

Collapse
 
ajstacy profile image
Andrew Stacy

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.

Collapse
 
starbist profile image
Silvestar Bistrović

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

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

Collapse
 
ajinkyax profile image
Ajinkya Borade

I think this article will get tailwind more users <3

Collapse
 
stephenh profile image
Stephen Haberman

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

Collapse
 
vadorequest profile image
Vadorequest

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.

Collapse
 
albanx0 profile image
Alban X

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

Collapse
 
palnes profile image
Pål Nes

Instead of SASS, one could use PostCSS

 
xcmk123 profile image
xcmk123

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