Wondering what’s even more challenging than choosing a JavaScript framework? You guessed it: choosing a CSS-in-JS solution. Why? Because there are more than 50 libraries out there, each of them offering a unique set of features.
We tested 10 different libraries, which are listed here in no particular order: Styled JSX, styled-components, Emotion, Treat, TypeStyle, Fela, Stitches, JSS, Goober, and Compiled. We found that, although each library provides a diverse set of features, many of those features are actually commonly shared between most other libraries.
So instead of reviewing each library individually, we’ll analyse the features that stand out the most. This will help us to better understand which one fits best for a specific use case.
Note: We assume that if you’re here, you’re already familiar with CSS-in-JS. If you’re looking for a more elementary post, you can check out “An Introduction to CSS-in-JS.”
Common CSS-in-JS features
Most actively maintained libraries that tackle CSS-in-JS support all the following features, so we can consider them de-facto.
Scoped CSS
All CSS-in-JS libraries generate unique CSS class names, a technique pioneered by CSS modules. All styles are scoped to their respective component, providing encapsulation without affecting any styling defined outside the component.
With this feature built-in, we never have to worry about CSS class name collisions, specificity wars, or wasted time spent coming up with unique class names across the entire codebase.
This feature is invaluable for component-based development.
SSR (Server-Side Rendering)
When considering Single Page Apps (SPAs) — where the HTTP server only delivers an initial empty HTML page and all rendering is performed in the browser — Server-Side Rendering (SSR) might not be very useful. However, any website or application that needs to be parsed and indexed by search engines must have SSR pages and styles need to be generated on the server as well.
The same principle applies to Static Site Generators (SSG), where pages along with any CSS code are pre-generated as static HTML files at build time.
The good news is that all libraries we’ve tested support SSR, making them eligible for basically any type of project.
Automatic vendor prefixes
Because of the complex CSS standardization process, it might take years for any new CSS feature to become available in all popular browsers. One approach aimed at providing early access to experimental features is to ship non-standard CSS syntax under a vendor prefix:
/* WebKit browsers: Chrome, Safari, most iOS browsers, etc */
-webkit-transition: all 1s ease;
/* Firefox */
-moz-transition: all 1s ease;
/* Internet Explorer and Microsoft Edge */
-ms-transition: all 1s ease;
/* old pre-WebKit versions of Opera */
-o-transition: all 1s ease;
/* standard */
transition: all 1s ease;
However, it turns out that vendor prefixes are problematic and the CSS Working Group intends to stop using them in the future. If we want to fully support older browsers that don’t implement the standard specification, we’ll need to know which features require a vendor prefix.
Fortunately, there are tools that allow us to use the standard syntax in our source code, generating the required vendor prefixed CSS properties automatically. All CSS-in-JS libraries also provide this feature out-of-the-box.
No inline styles
There are some CSS-in-JS libraries, like Radium or Glamor, that output all style definitions as inline styles. This technique has a huge limitation, because it’s impossible to define pseudo classes, pseudo-elements, or media queries using inline styles. So, these libraries had to hack these features by adding DOM event listeners and triggering style updates from JavaScript, essentially reinventing native CSS features like :hover
, :focus
and many more.
It’s also generally accepted that inline styles are less performant than class names. It’s usually a discouraged practice to use them as a primary approach for styling components.
All current CSS-in-JS libraries have stepped away from using inline styles, adopting CSS class names to apply style definitions.
Full CSS support
A consequence of using CSS classes instead of inline styles is that there’s no limitation regarding what CSS properties we can and can’t use. During our analysis we were specifically interested in:
- pseudo classes and elements;
- media queries;
- keyframe animations.
All the libraries we’ve analyzed offer full support for all CSS properties.
Differentiating features
This is where it gets even more interesting. Almost every library offers a unique set of features that can highly influence our decision when choosing the appropriate solution for a particular project. Some libraries pioneered a specific feature, while others chose to borrow or even improve certain features.
React-specific or framework-agnostic?
It’s not a secret that CSS-in-JS is more prevalent within the React ecosystem. That’s why some libraries are specifically built for React: Styled JSX, styled-components, and Stitches.
However, there are plenty of libraries that are framework-agnostic, making them applicable to any project: Emotion, Treat, TypeStyle, Fela, JSS or Goober.
If we need to support vanilla JavaScript code or frameworks other than React, the decision is simple: we should choose a framework-agnostic library. But when dealing with a React application, we have a much wider range of options which ultimately makes the decision more difficult. So let’s explore other criteria.
Styles/Component co-location
The ability to define styles along with their components is a very convenient feature, removing the need to switch back-and-forth between two different files: the .css
or .less
/.scss
file containing the styles and the component file containing the markup and behavior.
React Native StyleSheets, Vue.js SFCs, or Angular Components support co-location of styles by default, which proves to be a real benefit during both the development and the maintenance phases. We always have the option to extract the styles into a separate file, in case we feel that they’re obscuring the rest of the code.

Almost all CSS-in-JS libraries support co-location of styles. The only exception we encountered was Treat, which requires us to define the styles in a separate .treat.ts
file, similarly to how CSS Modules work.
Styles definition syntax
There are two different methods we can use to define our styles. Some libraries support only one method, while others are quite flexible and support both of them.
Tagged Templates syntax
The Tagged Templates syntax allows us to define styles as a string of plain CSS code inside a standard ES Template Literal:
// consider "css" being the API of a generic CSS-in-JS library
const heading = css`
font-size: 2em;
color: ${myTheme.color};
`;
We can see that:
- CSS properties are written in kebab case just like regular CSS;
- JavaScript values can be interpolated;
- we can easily migrate existing CSS code without rewriting it.
Some things to keep in mind:
- In order to get syntax highlight and code suggestions, an additional editor plugin is required; but this plugin is usually available for popular editors like VSCode, WebStorm, and others.
- Since the final code must be eventually executed in JavaScript, the style definitions need to be parsed and converted to JavaScript code. This can be done either at runtime, or at build time, incurring a small overhead in bundle size, or computation.
Object Styles syntax
The Object Styles syntax allows us to define styles as regular JavaScript Objects:
// consider "css" being the API of a generic CSS-in-JS library
const heading = css({
fontSize: "2em",
color: myTheme.color,
});
We can see that:
- CSS properties are written in camelCase and string values must be wrapped in quotes;
- JavaScript values can be referenced as expected;
- it doesn’t feel like writing CSS, as instead we define styles using a slightly different syntax but with the same property names and values available in CSS (don’t feel intimidated by this, you’ll get used to it in no time);
- migrating existing CSS would require a rewrite in this new syntax.
Some things to keep in mind:
- Syntax highlighting comes out-of-the-box, because we’re actually writing JavaScript code.
- To get code completion, the library must ship CSS types definitions, most of them extending the popular CSSType package.
- Since the styles are already written in JavaScript, there’s no additional parsing or conversion required.
Library | Tagged template | Object styles |
---|---|---|
styled-components | ✅ | ✅ |
Emotion | ✅ | ✅ |
Goober | ✅ | ✅ |
Compiled | ✅ | ✅ |
Fela | 🟠 | ✅ |
JSS | 🟠 | ✅ |
Treat | ❌ | ✅ |
TypeStyle | ❌ | ✅ |
Stitches | ❌ | ✅ |
Styled JSX | ✅ | ❌ |
✅ Full support 🟠 Requires plugin ❌ Unsupported
Styles applying method
Now that we know what options are available for style definition, let’s have a look at how to apply them to our components and elements.
Using a class attribute / className prop
The easiest and most intuitive way to apply the styles is to simply attach them as classnames. Libraries that support this approach provide an API that returns a string which will output the generated unique classnames:
// consider "css" being the API of a generic CSS-in-JS library
const heading_style = css({
color: "blue"
});
Next, we can take the heading_style
, which contains a string of generated CSS class names, and apply it to our HTML element:
// Vanilla DOM usage
const heading = `<h1 class="${heading_style}">Title</h1>`;
// React-specific JSX usage
function Heading() {
return <h1 className={heading_style}>Title</h1>;
}
As we can see, this method pretty much resembles the traditional styling: first we define the styles, then we attach the styles where we need them. The learning curve is low for anyone who has written CSS before.
<Styled />
component
Using a Another popular method, that was first introduced by the styled-components library (and named after it), takes a different approach.
// consider "styled" being the API for a generic CSS-in-JS library
const Heading = styled("h1")({
color: "blue"
});
Instead of defining the styles separately and attaching them to existing components or HTML elements, we use a special API by specifying what type of element we want to create and the styles we want to attach to it.
The API will return a new component, having classname(s) already applied, that we can render like any other component in our application. This basically removes the mapping between the component and its styles.
css
prop
Using the A newer method, popularised by Emotion, allows us to pass the styles to a special prop, usually named css
. This API is available only for JSX-based syntax.
// React-specific JSX syntax
function Heading() {
return <h1 css={{ color: "blue" }}>Title</h1>;
}
This approach has a certain ergonomic benefit, because we don’t have to import and use any special API from the library itself. We can simply pass the styles to this css
prop, similarly to how we would use inline styles.
Note that this custom css
prop is not a standard HTML attribute, and needs to be enabled and supported via a separate Babel plugin provided by the library.
Library | className | <Styled /> | css prop |
---|---|---|---|
styled-components | ❌ | ✅ | ✅ |
Emotion | ✅ | ✅ | ✅ |
Goober | ✅ | ✅ | 🟠 2 |
Compiled | 🟠 1 | ✅ | ✅ |
Fela | ✅ | ❌ | ❌ |
JSS | ✅ | 🟠 2 | ❌ |
Treat | ✅ | ❌ | ❌ |
TypeStyle | ✅ | ❌ | ❌ |
Stitches | ✅ | ✅ | 🟠 1 |
Styled JSX | ✅ | ❌ | ❌ |
✅ Full support 🟠 1 Limited support 🟠 2 Requires plugin ❌ Unsupported
Styles output
There are two mutually exclusive methods to generate and ship styles to the browser. Both methods have benefits and downsides, so let’s analyze them in detail.
<style>
-injected DOM styles
Most CSS-in-JS libraries inject styles into the DOM at runtime, using either one or more <style>
tags, or using the CSSStyleSheet
API to manage styles directly within the CSSOM. During SSR, styles are always appended as a <style>
tag inside the <head>
of the rendered HTML page.
There are a few key advantages and preferred use cases for this approach:
- Inlining the styles during SSR provides an increase in page loading performance metrics such as FCP (First Contentful Paint), because rendering is not blocked by fetching a separate
.css
file from the server. - It provides out-of-the-box critical CSS extraction during SSR by inlining only the styles required to render the initial HTML page. It also removes any dynamic styles, thus further improving loading time by downloading less code.
- Dynamic styling is usually easier to implement, as this approach appears to be more suited for highly interactive user interfaces and Single-Page Applications (SPA), where most components are client-side rendered.
The drawbacks are generally related to the total bundle size:
- an additional runtime library is required for handling dynamic styling in the browser;
- the inlined SSR styles won’t be cached out-of-the-box and they will need to be shipped to the browser upon each request since they’re part of the
.html
file rendered by the server; - the SSR styles that are inlined in the
.html
page will be sent to the browser again as JavaScript resources during the rehydration process.
.css
file extraction
Static There’s a very small number of libraries that take a totally different approach. Instead of injecting the styles in the DOM, they generate static .css
files. From a loading performance point of view, we get the same advantages and drawbacks that come with writing plain CSS files.
- The total amount of shipped code is much smaller, since there is no need for additional runtime code or rehydration overhead.
- Static
.css
files benefit from out-of-the-box caching inside the browser, so subsequent requests to the same page won’t fetch the styles again. - This approach seems to be more appealing when dealing with SSR pages or Static Generated pages since they benefit from default caching mechanisms.
However, there are some important drawbacks we need to take note of:
- The first visit to a page, with an empty cache, will usually have a longer FCP when using this method compared to the one mentioned previously; so deciding if we want to optimize for first-time users or returning visitors could play a crucial role when choosing this approach.
- All dynamic styles that can be used on the page will be included in the pre-generated bundle, potentially leading to larger
.css
resources that need to be loaded up front.
Almost all the libraries that we tested implement the first method, injecting the styles into the DOM. The only tested library which supports static .css
file extraction is Treat. There are other libraries that support this feature, like Astroturf, Linaria, and style9, which were not included in our final analysis.
Atomic CSS
Some libraries took optimizations one step further, implementing a technique called atomic CSS-in-JS, inspired by frameworks such as Tachyons or Tailwind.
Instead of generating CSS classes containing all the properties that were defined for a specific element, they generate a unique CSS class for each unique CSS property/value combination.
/* classic, non-atomic CSS class */
._wqdGktJ {
color: blue;
display: block;
padding: 1em 2em;
}
/* atomic CSS classes */
._ktJqdG { color: blue; }
._garIHZ { display: block; }
/* short-hand properties are usually expanded */
._kZbibd { padding-right: 2em; }
._jcgYzk { padding-left: 2em; }
._ekAjen { padding-bottom: 1em; }
._ibmHGN { padding-top: 1em; }
This enables a high degree of reusability because each of these individual CSS classes can be reused anywhere in the code base.
In theory, this works really great in the case of large applications. Why? Because there’s a finite number of unique CSS properties that are needed for an entire application. Thus, the scaling factor is not linear, but rather logarithmic, resulting in less CSS output compared to non-atomic CSS.

But there is a catch: individual class names must be applied to each element that requires them, resulting in slightly larger HTML files:
<!-- with classic, non-atomic CSS classes -->
<h1 class="_wqdGktJ">...</h1>
<!-- with atomic CSS classes -->
<h1 class="_ktJqdG _garIHZ _kZbibd _jcgYzk _ekAjen _ibmHGN">...</h1>
So basically, we’re moving code from CSS to HTML. The resulting difference in size depends on too many aspects for us to draw a definite conclusion, but generally speaking, it should decrease the total amount of bytes shipped to the browser.
Conclusion
CSS-in-JS will dramatically change the way we author CSS, providing many benefits and improving our overall development experience.
However, choosing which library to adopt is not straightforward and all choices come with many technical compromises. In order to identify the library that is best suited for our needs, we have to understand the project requirements and its use cases:
- Are we using React or not? React applications have a wider range of options, while non-React solutions have to use a framework agnostic library.
- Are we dealing with a highly interactive application, with client-side rendering? In this case, we probably aren’t very concerned about the overhead of rehydration, or care that much about extracting static
.css
files. - Are we building a dynamic website with SSR pages? Then, extracting static
.css
files may probably be a better option, as it would allow us to benefit from caching. - Do we need to migrate existing CSS code? Using a library that supports Tagged Templates would make the migration easier and faster.
- Do we want to optimize for first-time users or returning visitors? Static
.css
files offer the best experience for returning visitors by caching the resources, but the first visit requires an additional HTTP request that blocks page rendering. - Do we update our styles frequently? All cached
.css
files are worthless if we frequently update our styles, thus invalidating any cache. - Do we re-use a lot of styles and components? Atomic CSS shines if we reuse a lot of CSS properties in our codebase.
Answering the above questions will help us decide what features to look for when choosing a CSS-in-JS solution, allowing us to make more educated decisions.
Have you tried jayesstee?
No, I haven’t even heard about it. As I can see from the docs, it’s mostly a template engine, so it’s really out of the scope of my analysis, which is focused on CSS-in-JS using TypeScript and Next.js.
Or… You can use SCSS with BEM. I Tried many times and I still find that pseudo CSS in a component JS file is just pollution and nonsense.
I dislike having to scroll through crappy styling to see my true javascript code.
All of these features can be obtained with SASS or less. Css-in-js is an overengineered monster.
This article doesn’t debate “if” or “why” you should use CSS-in-JS. It’s dedicated for the persons that want to use this approach, but have a hard time figuring out which solution to choose.
So, if SCSS with BEM suits all of your needs, you don’t need anything else, of course.
I’ve used BEM since 2012, SCSS since 2009. They are both great and work nicely for relatively small projects and small teams. However, the lack of type-safety, refactoring tooling, or any way to enforce/check conventions other than code reviews, make them simply cumbersome to scale and maintain. Not to mention sharing type-safe design tokens (variables) between (S)CSS and TypeScript.
You can always move the CSS to another file for readability. You can also use CSS modules with SCSS for scoping, though you wont get the dead code elimination that CSS-in-JS offers. JS also offers the option for type safety (not the Eric Bailey flavor).
Having worked on large codebases with both approaches, BEM isn’t the appropriate tool anymore. You can still use SCSS with CSS modules, but manual CSS scoping is more error prone, regardless of developer skill. It’s a financial liability.
BEM does not scale. It just adds and adds to your CSS payload – the linear scaling problem highlighted in the article. It also doesn’t work well in big teams because the naming rules are too complex, there’s no way every team member will remember and apply them correctly. Time spent flagging inconsistencies in code reviews is time wasted.
I know there’s literally 50 of these, which is one reason I didn’t pursue this – but this was my attempt some years ago:
https://gist.github.com/mindplay-dk/47bc597dcbc28bea009b02c5a0c9010e
It’s object notation, but it lets you do SASS-like things, like nested selectors, continuations (the & operator) and media queries and so on. I was surprised at how little code that takes – I think this was less than 0.5K minified. I never could get the type checking to work properly for nested rules, I think that would be possible now. But of course the real work is precompilation and all that – I guess I gave up after doing the fun part.
Is there a finished library out there with the SASS-like features and nested object notation?
Most libraries that we’ve tested support:
– arbitrary nested selectors (except Treat)
– contextual styles, like “&” (except Styled JSX)
– media queries, keyframe animations
– object styles syntax (except Styled JSX and Linaria)
You can checkout the overview here:
https://github.com/andreipfeiffer/css-in-js/#overview
Thanks for explaining different ways to create and consume the styles in css in js. I am new to this css in js concept, whenever I look at code which is using cssinjs I got confused with all the ways they are using the cssinjs . Now after reading ur article I got the clarity.
Here you could add reachui, which is mentioned few tjmes in the community.
As dev with scss experience , we need to have better code complete/intellisense and need to migrate all the scss snippets and Emmet tricks when migrating to cssinjs.
I’m glad you found this useful :)
ReachUI and Material UI are design systems that also have their own components library. Indeed, they also provide a way to customise the styles, using some sort of CSS-in-JS approach.
My research is focused on solutions that can be applied outside the context of a specific design system. However, the general features presented here apply to these design systems implementations of CSS-in-JS as well.
Nice article, one other thing that might be good to mention would be the ability to use dynamic themes like emotions ThemeProvider. For me this is one of the other big advantages is this ability to do user defined white labelling.
Each solution differs in how they implement this too, emotion uses a context passed object and I think stitches uses css variables.
You’re right, I didn’t focus on the details of Theming, although most libraries covered here (with a few exceptions) provide some way to achieve that.
That could definitely be something worth exploring.
“Because there’s a finite number of unique CSS properties that are needed for an entire application. Thus, the scaling factor is not linear, but rather logarithmic, resulting in less CSS output compared to non-atomic CSS.”
The scaling factor is, as you put it, fixed to the number of unique CSS properties, so it’s constant, not logarithmic.
The number of unique CSS properties is constant “at a specific moment in time”. As your application grows, CSS usually grows as well. However, a certain amount of CSS properties you’ll reuse, because they were used before, so only a subset of the added CSS will contain new properties (or to be more specific “property/value pairs”).
So, in time, the CSS will grow up to a certain point, it won’t remain constant. The only way it could theoretically be constant is if you bundle “all the possible property/value pairs that you’ll ever use”, regardless if they are actually used or not. But that would not be optimal at all.
At least, that’s how I understand this Atomic CSS :)
Something that is conspicuously absent from this article is the topic of customizing components using styles. For example, suppose I make a “timeline” React component in which users can choose a time range using a slider and a pair of date pickers. Sure, the timeline needs some CSS, but it might well be sufficient to specify inline style={}s.
Since this is a generic component, I would prefer NOT to dictate the styling inside the component itself (except its default appearance). I want the component to allow styles to be provided by the caller/parent component. Which CSS libraries would help me do that?
As far as my understanding goes, your use case is not strictly related to CSS-in-JS, it’s more of a Component’s public API problem. It really depends on what level of customisation you want to expose to the consumers of your component:
you can have a theming system to allow full customisation for all components (useful for customising a whole library of components for your application)
or you can allow full customisation and styles overriding (useful for one-off, not re-usable variations)
You probably refer to the 3rd scenario. That’s something that depends strictly on the “Styles Applying” method:
– if the library uses className strings, then your component can receive an additional
className
prop defined on the consumer, which will be concatenated in the component where needed– with styled components, you can create new components based on existing components, but with different styles
– with the css prop, you can simply pass an additional object to the component, and the library will take care of merging the styles
This is something that can be explored in detail, of course. I will add it as an issue and analyse it in the near future.
Should check out Aesthetic, which offers a design system coupled with a CSS-in-JS solution. https://aestheticsuite.dev
The link provided doesn’t help me understand this conclusion. Faster in what context?
In building the CSSDOM? I Assume a class has to do a lookup to find the styles, that should be slower right?
Hi Drew,
The provided link has a “Results” tab on top (next to the “Overview” tab).
You can click “Run benchmark” to run the test suite of various trivial implementations to update the styles of a node.
You can check the implementations also in each individual tab, next to “Results”.
NOTE: It’s not my own implementation, I haven’t ran my own benchmarks. This is an example I found while searching for such resources.
I’ve never understood the attraction of styled-components’ way of declaring CSS. Why should you need to instantiate a class when you declare styles? Does that not reduce reusability? If I have a group of declarations that define a certain typographic style, I might want to use them in a paragraph, a caption, a td or elsewhere. Why would I want to tie it to a particular element?
I know what you mean, I feel the same, because I’ve used the standard approach for so many years:
1. define the styles in a CSS class
2. apply the class to html elements
With Styled components syntax, the HTML element definition and the styles definition is a single step.
If you think about encapsulation, meaning that “every component should encapsulate its own styles”, this syntax really makes sense.
If you want to reuse certain styles, you can always compose them, or interpolate them if you’re using template string syntax.
Or, you can create a base component and then extend it to define specific components. Of course, you might argue “why should I create a base component if I don’t explicitly need it?”. Well, the harm it does is truly negligible. And also consider all the other benefits before focusing on a single downside.
Just to be clear, I’m not defending them. I don’t use that syntax neither as it doesn’t suit me personally. I’m only trying to objectively understand the tradeoffs. And if I were to work on a project that already uses SC, I could find many benefits to this approach.
Your unit of reuse is not a class but a component. You’d reuse it the same way. Think of it as wrapping something with a custom HTML element instead of adding a property to an existing one.
That css littral string needs to be coverted to js is not true, there is an alternative direct injection method with document.adoptedStyleSheets
see https://github.com/WebSVG/voronoi/blob/cd1398db026b65f4cab40619c7740b0188627db9/src/scale-grid.js#L117
From the MDN docs:
The adoptedStyleSheets […] is used for setting an array of constructed stylesheets.
So, whatever you pass to adoptedStyleSheets(), you need to instantiate with CSSStyleSheet(), which uses Objects.
But even if you pass a string to the DOM API, it will still need to parse it, validate it, convert it to Object and merge it with the existing CSSOM.