Editor’s note: This article was last updated on 1 September 2023 to incorporate information about the vanilla-extract and Treat runtime libraries, as well as to update the code segments relating to the Linaria library.
Web developers often search for repositories on GitHub to contribute to. However, once they find an exciting project and start contributing, a common challenge they face is finding the specific style definitions of the project, especially in large projects.
Fortunately, there’s a simple solution for this: web developers should define components and styles in the same project file. The CSS-in-JS syntax technique is used to write components and styles in the same file while maintaining simplicity and clarity.
According to styled-components creator Max Stoiber, more than 60 percent of React installs also install a CSS-in-JS library. Writing CSS in JavaScript is extremely popular, especially when using a JS framework like React or Angular. Many libraries are available to simplify your process of writing CSS-in-JS. In this article, we’ll review some of the most popular options.
CSS-in-JS is a styling technique where components are styled directly by using JavaScript. We use variables to define the CSS of the component. The variable will contain all the CSS properties, ensuring that the component is seamlessly wrapped with its specified styles.
CSS-in-JS has increased in recent times as component-based styling has also become more popular. And as most of the modern JavaScript frameworks are component-based, this fuels the increased usage of CSS-in-JS. CSS has now become a module of JavaScript that can be defined and used whenever necessary.
In a modern framework like React, you can use the inline technique to write CSS in the JSX section of your JavaScript file for by writing CSS-in-JS. But this technique can be confusing, less readable, and can break the flow of code. It can’t replace the modularity of CSS while writing CSS-in-JS through libraries.
In CSS-in-JS, we define styles in a variable, which we can then use to style the components by wrapping them with the variable tag.
The styled
tag is imported from the library. It creates a React component with pre-defined styles, followed by the HTML tag that you want to use. In the example below, we use h1
, which will be customized according to the defined CSS properties. After coding this, we then define the properties like so:
const Title = styled.h1` font-family: sans-serif; font-size: 48px; color: #f15f79; `;
Next, we wrap the contents within the variable tag:
const App = () => <Title>Hello world!</Title>;
Viola! This is how you define the styles in most CSS-in-JS libraries. Now that we’ve covered the basics, let’s look at some advantages of using CSS-in-JS.
There are a few downsides to using CSS-in-JS, including:
High-level programming languages designed for developing applications are usually created for humans to read and understand. For a machine to understand code written in a high-level language like JavaScript or Python, the code needs to be transformed by a compiler or interpreter into low-level, machine-readable code.
Compile time refers to the time when code written in a high-level, human-readable language is transformed by a compiler into a low-level, machine-readable code. Runtime, on the other hand, refers to the time when a program’s transformed code is running on the end user’s machine.
One of the solutions to improving lost performance time due to double parsing is that the libraries can convert the CSS-in-JS block into a separate CSS file first. Then, the browser will read and apply those styles to the webpage, ultimately saving runtime that’s typically wasted while generating a style tag. This is called zero-runtime CSS-in-JS. It is particularly useful for scaled or complex projects where performance is key.
To achieve zero-runtime CSS-in-JS, there are various libraries you can make use of. In the following sections, we’ll introduce some of the most popular ones to consider.
Written in TypeScript, Linaria is the most popular zero-runtime library, with 10.8k GitHub stars and 429 GitHub forks. It’s my personal favorite to use because it has an easy-to-use syntax and is compatible with almost every modern framework. Linaria converts the CSS-in-JS into a separate .css
file while creating the build for production.
Linaria offers many features, including:
true
while writing CSS, it will show the source of the class name of generated CSS in dev toolsInstall Linaria from the npm package registry like so:
npm i @linaria/core @linaria/react @linaria/babel-preset
And here’s how you can use Linaria in your project:
import { css } from "@linaria/core"; import { modularScale, hiDPI } from "polished"; import fonts from "./fonts"; // Write your styles in `css` tag const header = css` text-transform: uppercase; font-family: ${fonts.heading}; font-size: ${modularScale(2)}; ${hiDPI(1.5)} { font-size: ${modularScale(2.5)}; } `; // Then use it as a class name <h1 className={header}>Hello world</h1>;
Disadvantages of using Linaria:
Astroturf is a great alternative to Linaria. With more than 2.2k stars on GitHub, Astroturf helps you achieve zero runtime by keeping CSS fully static with no runtime parsing. With Astroturf’s scoped stylesheet, React, and props and component variants, there are several ways to write CSS-in-JS. See the implementation here.
The key features of Astroturf include:
Install Astroturf from npm with the following command:
npm i astroturf
Here’s how you can use Astroturf in your project:
import React from "react"; import { css } from "astroturf"; const btn = css` color: black; border: 1px solid black; background-color: white; `; export default function Button({ children }) { return <button className={btn}>{children}</button>; }
Disadvantages of Astroturf:
Written in JavaScript, Reshadow has 364+ stars on GitHub. This library offers many features, most notably delivering a shadow DOM developer experience for virtual DOM-like frameworks such as React. It also supports the CSS-in-JS syntax.
Reshadow offers the following features:
.css
fileFirst, install Reshadow from npm:
npm i reshadow
And here’s how to use it in your project:
import styled, { css } from "reshadow"; const styles = css` button { font-size: 16px; cursor: pointer; padding: 10px 15px; border-radius: 20px; border: 2px solid; background-color: white; color: darkorange; } `; const Button = ({ children, ...props }) => styled(styles)(<button {...props}>{children}</button>);
Disadvantages of Reshadow:
vanilla-extract is another popular zero-runtime CSS-in-JS library with over 8.7k stars on GitHub. It is framework agnostic, which means that you can use it with vanilla JavaScript or any frontend framework.
With vanilla-extract, you can write locally scoped styles and variables in JavaScript or Typescript and generate CSS files at build time. It also offers an optional API for dynamically theming your application.
Some of the features and benefits of vanilla-extract include:
To start using vanilla-extract, install it from the npm package registry:
npm install @vanilla-extract/css
You need to declare your styles in .css.ts
or .css.js
files when working with vanilla-extract:
import { style, globalStyle } from "@vanilla-extract/css"; export const buttonStyles = style({ backgroundColor: "#1e4db6", color: "#fff", padding: "10px 20px", borderRadius: "5px", border: "none", }); globalStyle("body", { backgroundColor: "#eee", margin: 0 });
Import the style you have declared from the .css.ts
or .css.js
file and apply it using the class
attribute (or className
, if you’re using React) like in the example below:
import { useState } from "react"; import { buttonStyles } from "./app.css"; function App() { const [count, setCount] = useState(0); return ( <button className={`${buttonStyles}`} onClick={() => setCount(count + 1)}> Count: {count} </button> ); } export default App;
Disadvantages of vanilla-extract:
Treat is another popular zero-runtime CSS-in-JS package with over 1.2k stars on GitHub. Like the other packages discussed above, when using Treat, styles are declared in .treat.js
or .treat.ts
files. Treat executes the .treat.js
files and generates all CSS rules at build time, bundling only the generated CSS styles.
If you are using the theming feature, Treat generates all the CSS styles at build time. It then swaps the pre-generated classes at runtime when switching themes.
While Treat offers native support for React and TypeScript, its core API can be integrated into other frameworks, making it framework agnostic.
Some of the benefits of using Treat include:
You can install Treat from npm like so:
npm i treat
After installation, you can declare your styles in a .treat.js
or .treat.ts
file like this:
import { style } from 'treat'; export const buttonStyle = style({ backgroundColor: "#1e4db6", color: "#fff", padding: "10px 20px", borderRadius: "5px", border: "none", });
You can then import the styles you have declared into your component, like in the example below. This example assumes you’re using React:
import React from 'react'; import { buttonStyle } from './Button.treat'; export const Button = () => { return <button className={buttonStyle}>Click me</button> }
Disadvantages of Treat:
Goober is a popular, lightweight, zero-dependency package. While it isn’t strictly a zero-runtime CSS-in-JS solution, its built-in extractCss
function allows you to extract static CSS files and inject them into the <head>
tag as you would in zero-runtime CSS-in-JS packages during server-side rendering.
The advantages of using Goober include:
You can install Goober from npm with this command:
npm i goober
You can then inject the styles into your server-rendered code like so:
const express = require("express"); const { styled, extractCss, setup } = require("goober"); const preact = require("preact"); const render = require("preact-render-to-string"); setup(preact.h); const app = express(); const Button = styled("button")` background-color: #1e4db6; color: #fff; padding: 10px 20px; border: none; border-radius: 10px; `; const App = () => preact.h(Button, null, "Click me!"); app.get("/", (_, res) => { const app = render(preact.h(App)); const style = extractCss(); const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SSR</title> <style id="_goober"> ${style} </style> </head> <body> ${app} </body> </html> `; res.send(html); }); app.listen(process.env.PORT || 3000);
Disadvantages of Goober:
Zero-runtime CSS-in-JS libraries offer many benefits, especially when developers are contributing to multiple or large projects. The future of CSS-in-JS is bright, especially when it comes to web development and modern frameworks. I hope you will add one of these libraries when you initiate your next project.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.