Why CSS-in-JS?
Tags: css, javascript, cssinjs, webdev
Published: 2022-08-15 09:10:55

## Global and cascading nature of CSS

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

```css
html {
	font-family: Roboto, sans-serif;
}
```

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.

```html
<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>
```

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. 

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

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

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.

```jsx
.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
}
```

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

```jsx
const uniqClassName = css({ textAlign: 'center' });
// is converted as 
// .css-123 { text-align: center; }

console.log(css({ textAlign: 'center' });  // css-123
```

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

```jsx
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'
	}

*/
```

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. 

```jsx
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;         
// }
```

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.

```jsx
const styleObject1 = {
	textAlign: 'center'
};

const styleObject2 = {
	textAlign: 'center'
};

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

css(styleObject1) === css(styleObject2) // true: class-name is same
```

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

```jsx
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
```

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

```jsx
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
```

 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](https://stitches.dev/) and [vanilla-extract-css](https://vanilla-extract.style/) are looking promising by making everything build time process so this is not going to be a problem in future. A framework like [tailwind](https://tailwindcss.com/) with **atomic-css** is something that is missing in CSS-in-JS world. I’m hopeful for [stylex a Facebook](https://youtu.be/9JZHodNR184?t=213) 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](https://vanilla-extract.style/). I'm a big fan of [tailwind](https://tailwindcss.com/) 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](https://twind.dev/) 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](https://stitches.dev/) and [vanilla-extract-css](https://vanilla-extract.style/).