Why is CSS-in-JS no-nonsense?
Let’s begin with basics, CSS-in-JS is not any library nor a framework. If this information surprises you, you are not alone as I heard even experienced frontend developers answering me something like: “CSS-in-JS? Yeah, I heard about that library”. CSS-in-JS (yup, that term would appear many times along this article. I don’t know how to call it in any other way. You can try to count this phrase while reading this article, answer is at the end) means that we are going to write styles rules using appropriate javascript packages. Two of them that I will surely mention below are emotion-js and styled-components. Such a solution lets us easily encapsulate code, prevent accidentally overwrite styles, very efficiently solves new version styles cache killing (or not) and it is just simply easier and nicer to read code that presents structure and styles of component in one place.
Despite the multitude of its advantages, you can also see clear disadvantages of such a solution. An example would be possible syntax differences between different packages. At a time when the only dilemma in choosing CSS preprocessors was the question whether to choose SCSS, LESS or maybe stick to plain CSS with BEM methodology, the syntax between them was the same. In fact, it was still “clean” css. In our case it may be different, because although the most common difference is camelCase vs dash-case, with more code it can be problematic. In addition, there is a way to pass dynamic data inside (thankfully we can already use css variables so it is no new thing). Let’s move on to the more optimistic aspects of the solution that is not nonsense.
Long story short, how CSS-in-JS really looks like?
After some introduction, I think that this is the proper time for the example. We have two leading solutions that implements CSS-in-JS: emotion and styled-components. Both of them have pretty similar syntax so there is no really big difference in code. To be honest, these are both of the examples, first let’s look at emotion
:
import styled from "@emotion/styled";
const Button = styled.button`
padding: 32px;
background-color: hotpink;
font-size: 24px;
border-radius: 4pxl
color: black;
font-weight: bold;
&:hover {
color: white;
}
`;
render(<Button>This is my button component.</Button>);
and then styled-components
:
import styled from "styled-components";
const Button = styled.button`
padding: 32px;
background-color: hotpink;
font-size: 24px;
border-radius: 4pxl
color: black;
font-weight: bold;
&:hover {
color: white;
}
`;
render(<Button>This is my button component.</Button>);
But they are the same… are they?
Pretty the same, huh? So, what’s the difference you may ask? Both of them support such things as passing the props, handling media queries, global styles, nested selectors and even such important things like server side rendering. Emotion has almost three times smaller bundle size (styled-components 45.1kB vs 15.5 kB of emotions) and emotion performance is way better than styled-components (render time) however some devs may choose which library to use considering active community and github star amount, so styled-components beats emotion in tracks, because 27k of stars are still more trustworthy than almost 10k. Let’s talk about numbers, I made simple test. I have rendered 100k buttons created with both of libraries, emotion and styled-components, these are the numbers and charts:
- Emotion rendering phases
- Emotion rendering timeline
- Styled-components rendering phases
- Styled-components rendering timeline
You do the math.
What is the real difference for me as a code writer?
Ok, ok, we are talking about performance, some github stars, syntax and more other stuff. Why should we even reject old, traditional way to simply link cascade style sheets in our index.html? Or even just use some SCSS or LESS loader in webpack and easily import styles in jsx files? Here is the answer: simplicity. Yup. Maybe it may look a little complicated but let’s recall times when JSX was introduced and everybody’s faces were just something like 😐 emoji. Some of us surely said that mixing html in javascript would be hard to read and problematic, but finally it turned out to be a great solution. So, why not doing the same with styles? There are so many reasons. We can get rid of native html elements tag names with classes. From now on we can replace something like this:
<div className="icon icon--horizontal">
<i className="icon__glyph icon__glyph--big" />
<span className="icon__label">Lorem ipsum</span>
</div>
With something like this
<Icon oriention="horizontal">
<Glyph size="big" />
<Label>Lorem ipsum</Label>
</Icon>
You see now which one is easier to read and remember, it is only just a simple example with containered text and icon. Imagine a whole big, 100 or 200-lines component written in both ways and choose the simplest one. Yeah, choice is easy, I know. Of course, you can use some webpack plugin that will hash or obfuscate all of your classes and other attributes, but why if you have something ready to use?
Another pro about emotion or styled-components or pretty any other CSS-in-JS library is cache killing. If you are using old school class names from the example provided previously they are staying the same after the new version release so some of the browsers cannot see the difference even if there are some. Emotion (for example) provides you complete hashing system out of the box and the example is below
const SomeDiv = styled.div`
...;
`;
render(<SomeDiv />);
it appears on development environment as
<div class="SomeDiv-lilb908"></div>
and on production as
<div class="css-lilb809 eogvql60"></div>
As you can see there is no problem with recognizing elements in inspector when you are still writing code and of course production class names are changing alongside with new release. Some problems may occur if you try to test the production environment, but this is another story 😅. Hmm, what about modifying styles due to changing component state? Our styled component, doesn’t matter what library we are using, is still just a React component, so we can still pass props to it. What would it look like in code? Let’s say that we have a button which changes his color on click event.
import ...
const Button = styled.button`
border-radius: 3px;
width: 100px;
height: 50px;
background-color: ${({ redBackground }) => redBackground ? 'red' : 'blue'};
`;
function App() {
const [redBackgroundColor, setBackgroundColor] = useState(false);
function toggleButtonBackgroundColor() {
setBackgroundColor(!redBackgroundColor);
}
return(
<Button
redBackground={redBackgroundColor}
onClick={toggleButtonBackgroundColor}
>
Lorem ipsum
</Button>
);
}
As you can see there are no additional class modifiers nor class or ids. It is just a directly modified style depending on the component state. I think this is a good occasion to mention inline styles. Of course, we can do that in as simple way as creating styled components, look to this example:
import ...
const Button = styled.button`
border-radius: 3px;
width: 100px;
height: 50px;
`;
function App() {
const [redBackgroundColor, setBackgroundColor] = useState(false);
function toggleButtonBackgroundColor() {
setBackgroundColor(!redBackgroundColor);
}
const colorModifier = redBackground => css`
background-color: ${({ redBackground }) => redBackground ? 'red' : 'blue'};
`;
return(
<Button
onClick={toggleButtonBackgroundColor}
style={colorModifier(redBackground)}
>
Lorem ipsum
</Button>
);
}
Drawbacks, if they are really them.
Of course there are some minor cons about using mentioned libraries. But do we have to call it drawbacks? There are some troubles during migration, of course there are, but nothing is impossible and it may sometimes pay us back with nice rewards.
- Syntax highlighting will not work, at least without some effort. And in some editors.
- Linters will mostly not work.
- You can’t use LESS, SCSS or PostCSS — or any of your existing mixins. The good news is many new plugins appear in VS code or webstorm market that can help with CSS-in-JS implementation so syntax highlighting or linting may not be so painful. The bad news is we can have tons of mixins used along codebase, and we have to rewrite almost all of them, but how complicated can the mixin be that we cannot rewrite it to beloved javascript, huh? 😅
Oh, and CSS-in-JS appeared 9 times in this article.