DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,274 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Why CSS-in-JS?
Late Night Coder
Late Night Coder

Posted on

Why CSS-in-JS?

Global and cascading nature of CSS

CSS is global by design! It is that way because we want to bring consistency across the website.

html {
    font-family: Roboto, sans-serif;
}
Enter fullscreen mode Exit fullscreen mode

By writing the above code every text on your website has a font-family set. And this is by design instead of setting font-family on each item.

This very nature of being global and cascading creates problems when styles in parents cascade into children. The problem doesn’t end there the devil called β€œspecificity” in CSS is always there to bring you surprises.

<style>
    #wrapper {
        background-color: green;
    }

    .background-red {
        background-color: red;
    }

    .background-orange {
        background-color: orange;
    }

</style>
// case 1
<div id="wrapper" class="background-orange">
    Box 1
</div>
// case 2
<div  class="background-orange background-red">
    Box 2
</div>
Enter fullscreen mode Exit fullscreen mode
  1. As the id has higher specificity over class the background color is green.
  2. As the occurrence of background-orange is later in the style so it will override even though in class attribute background-red is used later.

Scoping of CSS per component should solve the global issue of CSS. The scoping CSS problem is solved differently by different technologies.

Web components provide scoping CSS into shadow dom which doesn’t leak anything outside and also doesn’t let other things affect it i.e scoping it into the component and closing at the same time. Frameworks like Vue also have scoped style solutions in place. The web components provide a very strict boundary and sometimes we don’t need such a strict boundary.

The architecture naming of CSS classes using BEM i.e .block_element--modifier tries to solve the issue by following naming conventions but this isn’t absolute there are ways in which scope leaks can happen.

A framework like Vue and Angular has its own way of scoping styles to elements/components built in.

What CSS-in-JS does do for you?

It scopes all the styles into a unique class-name thereby solving the problem of global scope. Now each item has its CSS scoped to a unique class-name.

import { css } from "css-in-js";

function Flex(props) {
    return (
        <div className={css({ display: "flex" })}>
            {props.children}
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

In the above code:

  • The css function from the CSS-in-js lib takes in style-object
  • A style-object is nothing but a way of writing CSS with javascript objects.
.text-center 
// following is how css is declartion is written in a .css file -- 1
{
    text-align: center;
}

// the same object as style-object in javascript is written as -- 2
{
    textAlign: 'center' // split on changed casing then joined by "-" and lowercased
}
Enter fullscreen mode Exit fullscreen mode
  • The style-object is converted into a valid CSS declaration and is scoped to a unique class-name and css function then returns that unique class-name.
const uniqClassName = css({ textAlign: 'center' });
// is converted as 
// .css-123 { text-align: center; }

console.log(css({ textAlign: 'center' });  // css-123
Enter fullscreen mode Exit fullscreen mode
  • The class-name is cached for the given style-object and whenever the same style is passed { textAlign: 'center' } to css function it will always yield css-123 , this is an optimization step.

  • On the problem of specificity, it cannot solve for case-1 because using id for styling indicates poor CSS architecture. For the later case-2 it will solve it as:

css([ 
    { display: 'flex', backgroundColor: 'red' }, 
    // conflicting declarations backgroundColor
    { flexDirection: 'column', backgroundColor: 'orange' } 
])

// styles objects in [] are merged and thus resultant style object is

/*

    {
        display: 'flex',
        flexDirection: 'column',
        backgroundColor: 'orange'
    }

*/
Enter fullscreen mode Exit fullscreen mode

The above solves the specificity by applying the last declaration overriding others that came before it.

Problems with CSS-in-JS

  • Repetition of styles. Even though the two style-object vary by a very small bit there is an entirely new class-name for two.
css({ display: 'flex', flexDirection: 'row' })
// .css-1234 {
//   display: flex; // <-- same declaration for display
//   flex-direction: row;         
// }
css({ display: 'flex', flexDirection: 'column' })
// .css-9876 {
//   display: flex; // <-- same declaration for display
//   flex-direction: column;         
// }
Enter fullscreen mode Exit fullscreen mode

If you notice, the two declarations vary only in values of flexDirection values but they will have entire different class-name, this is not a problem as it is by design to have all styles scoped uniquely under a unique class-name but the fact that the property display is repeated means something better can be done.

  • We generate a unique class-name for a style-object and as the same structured style-object always returns the same class-name.
const styleObject1 = {
    textAlign: 'center'
};

const styleObject2 = {
    textAlign: 'center'
};

styleObject1 === styleObject2 // false: object ref is diff

css(styleObject1) === css(styleObject2) // true: class-name is same
Enter fullscreen mode Exit fullscreen mode

This can be only achieved by when there is a phase of stringifying the style object followed by hashing to generate a unique name.

JSON.stringify(styleObject1) === JSON.stringify(styleObject2); 
// true: same stringified object value for the same structured object
// now hashing will return the same output as the input 
// JSON.stringify(styleObject1), JSON.stringify(styleObject2) are same
Enter fullscreen mode Exit fullscreen mode

Depending on the logic of stringifying the object the complexity & time may vary.

const object1 = { a: 1, b: 2 }; 
const object2 = { b: 2, a: 1 }; // <-- looks same but structerly different

JSON.stringify(object1) === JSON.stringify(object2) // false: diff structure
Enter fullscreen mode Exit fullscreen mode

And so, depending on the logic of stringifying algorithm we can make object1 and object2 string representations look the same. It may or may not be a concern for lib to output the same class-name for same looking object and that will require some work! Most of the time it won’t be a concern as repeating a few class-name doesn’t matter much, but do note that there is always a hashing step involved usually these are quick and insecure hashing algorithms to optimize for speed.

  • When using React with CSS-in-JS there is a cost of injecting styles on every render along with the phase of stringifying to generate a class-name that will happen on every render. The new libraries like stitches and vanilla-extract-css are looking promising by making everything build time process so this is not going to be a problem in future. A framework like tailwind with atomic-css is something that is missing in CSS-in-JS world. I’m hopeful for stylex a Facebook internal atomic CSS-in-JS to provide the best of both world.

  • Love for preprocessors and pure CSS is not going to die that easily and it shouldn’t for the fact that CSS-in-JS is not needed for every website or it is just hard for UI-dev to wrap their minds around CSS-in-JS or view-encapsulation may just not be a problem for your project or you CSS architecture (or even using BEM naming convention) may just not have a need for it.
    Also, CSS itself is evolving and I'm hopeful for a future where scoping will be built into CSS.

Conclusion

The CSS-in-JS library solves problems of global nature of CSS and of specificity by providing scoping in a unique class-name. It has some cost attached to it i.e run-time which is being solved by order libs vanilla-extract-css. I'm a big fan of tailwind and I honestly believe it is enough for your project. If you also need dynamic styles then CSS-in-JS is better over tailwind, though there are solutions like twind which provide a flavor of tailwind with the CSS-in-JS approach they do have all cons of any CSS-in-
JS libraries. I'm very excited about styles by Facebook and waiting for the day it will be open-sourced or CSS itself evolves to me provide scoping and be more modular, until that day comes I'm betting on CSS-in-JS with stitches and vanilla-extract-css.

Top comments (1)

Collapse
 
homyeeking profile image
Homyee King

is there any atomic-css-in-js framework ?

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

β­οΈπŸŽ€ JavaScript Visualized: Promises & Async/Await

async await