DEV Community

Discussion on: Why We're Breaking Up with CSS-in-JS

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
 
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
 
juliansoto profile image
Julian Soto

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

Collapse
 
leob profile image
leob

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

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

Thread Thread
 
dwayne profile image
Dwayne Crooks

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?

Thread Thread
 
markohologram profile image
Marko A

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.

Collapse
 
airtonix profile image
Zenobius Jiricek

There's linting you can use to enforce this.

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