CSS-in-JS is an idea to tackle various issues with CSS by writing them in JavaScript (or whatever else that can compile to JavaScript, such as TypeScript). Some popular examples include libraries like styled component , emotion and JSS .
I originally started this series to document some of my experiments with CSS-in-JS, but it later turned out to be a fun investigation project. In total, this series will cover
- The basic idea of run-time static CSS-in-JS (this post)
- The basic idea of run-time dynamic CSS-in-JS
- Run-time dynamic CSS-in-JS with CSS variables
- An extremely simplified approach to build-time CSS-in-JS
- A simple approach to implementing tagged template based CSS-in-JS
Please note that this series focus on the high level ideas with a simple demo, so expect to notice
a lot of missing features. In particular, there’s no mention of at-rules or &
parent selectors.
Adding them shouldn’t be very hard, but will likely make this series even longer.
The basic idea of run-time static CSS-in-JS
Credit: some code here is inspired by nano-css .
End Result
We will build a simple library that allows us to do this:
We will expand this to support dynamic styles in the next post . For now, the library is only limited to static styles.
Using the CSSStyleSheet API
This is a relatively obscure DOM API that allows programmatic manipulation of CSS rules. More details can be found on mdn .
Here’s a high level overview of how to use this API for our purpose:
Firstly, we can create a <style>
tag:
|
|
The DOM API allows us to access the stylesheet object representing the styles in
the <style>
tag by sheetElement.sheet
.
The stylesheet object allows us to manipulate the css rules, e.g. we can insert a rule by:
|
|
With this API, we can implement a CSS-in-JS library like so:
- Generate a class name, then
- Attach the rules to that class name, then
- Assign the class name to some DOM element
Example implementation
Defining our in-JS representation
Firstly, let’s define the type of CSS objects. Here we want the ability to specify CSS rules with a nestable object representation so that we can specify rules with selectors. At the same time we want to be able to not specify the root class name in the object and automatically generate a unique one when necessary.
|
|
We use the csstype
library to set up the basic css types. CssLikeObject
is either
some css representation or some nested CSS representation object.
Here’s an example of what a CssLikeObject
may look like:
|
|
As you can see, the nestable representation allows us to declare richer CSS rules that supports things like selectors and psudoelements.
Convert from JS representation to CSS string
But wait, how do we convert from CssLikeObject
to string
? First, let’s visualize how this
CssLikeObject
can be turned into css.
Assuming that the generated class name is .gen-class-1
, the final output should be:
|
|
We present an algorithm below to generate a set of CSS rules, each represented by a string. Comments inline.
|
|
We also introduced a joinSelector
function because we need to treat
states and psudoelements differently from descendant selectors.
|
|
Generating class names
We haven’t really talked about generating class names yet. Here we are going to go with a simple counter. In some cases a stable hash may be better, but for our little toy example this is sufficient.
Having a counter introduces some states. Plus, we need to create a style
element, which is itself a state. It’s time to make a class for all these.
We are going to call this StyleDef
.
|
|
We also introduce a prefix
string so that multiple StyleDef
s won’t create classes that
conflict with each other.
With that, we are done! You can play with the code in here .
Issues with CSS-in-JS in general
Now that we have built a small library, it’s time to talk about some issues with CSS-in-JS frameworks.
Performance
We can notice a few issues with our little library:
- Instead of downloading and potentially parsing JS and CSS file in parallel, style code now needs to be parsed with JS code in series.
- There’s some overhead in first generating CSS strings through JS, before having them parsed by the browser, compare to just giving the browser some raw CSS files.
The point is that there’s always some overhead in using CSS-in-JS compared to loading CSS files directly. For a small to medium project, this is likely not a problem, and the ease of development will likely outweigh the performance cost. For any massive projects, this can mean a large performance hit.
Build step to the rescue?
One solution here is to use CSS-in-JS during dev, but compiles them into CSS for build. However, this means that there is now a build step that needs maintaining and takes time. This build step is also potentially more complex than other css preprocessors like SCSS, because it needs to read JS/TS code, which has their own dependency trees and then emit CSS code, then replace some of the JS code output with the emitted CSS class name.
Additionally, using a build script usually means that dynamic styles has to be supported by CSS variables, which introduce its own limitation. The now deprecated emotion static extrator and linaria are two examples.
Ease of debugging
It used to be that CSSStyleSheetAPI takes away our ability to edit style rules directly in browser inspectors. This has since been fixed in Chrome and Firefox, though it’s still an issue in “the new IE” .
How about Inline styles?
This is one other approach in the CSS-in-JS field. However, it has two issues:
- A lot of CSS features aren’t available - no pseduelements, selectors, media queries, states etc.
- Every single element defines its own styles - this can lead to some concerns on performance due to a larger DOM representation per node and likely more DOM mutations
On the other hand, based on what we have just discussed, inline styles also has
some perf advantage over our approach - we get to directly manipulate styles by
modifying attributes of element.style
rather than getting the browser to parse
our CSS strings, though some parsing still has to be done as the browser needs to
parse the property values.
Radium is an example that uses this approach and works around 1 by listening on DOM events to trigger special state or media queries.
ES6 tagged templates
A lot of the popular CSS-in-JS libraries allows for syntax like this
|
|
This is done with ES6 tagged templates , which we will explore in a future post.
Things to look forward to
Why on earth can’t CSSStyleSheet support an API like inline styles, where we can set properties directly instead of having to pass raw css strings around? Well, CSS Typed Object Model does exactly that. However, it’s still in experimental stage, and only Chrome and its bastard child Edge support it as of now.