Zero-Runtime CSS-in-JS: Combining Developer Experience with Exceptional Performance

3 min

1. The “Double-Edged Sword” of CSS-in-JS

CSS-in-JS solutions such as styled-components and Emotion transformed style management in component-based development by allowing developers to write CSS inside JavaScript or TypeScript files. They offer many benefits:

  • Component scope: Styles are associated with components by default, solving global CSS naming conflicts.
  • Dynamic styles: Dynamic styles can easily be created from component props or state.
  • Code colocation: Styles and component logic live in the same file, making them easier to maintain and organize.

These conveniences, however, come at a cost. The core of traditional CSS-in-JS libraries is the Runtime. When a component mounts in the browser, the CSS-in-JS library’s JavaScript runtime:

  1. Parses CSS in template strings or objects.
  2. Generates unique class names.
  3. Dynamically injects the styles into the document’s <head>, inside <style> tags.

This process increases the JavaScript bundle size and adds computational overhead during application startup and rendering, affecting performance.

2. How “Zero Runtime” Breaks the Deadlock

To solve runtime performance problems, the community proposed a new approach: Zero-Runtime CSS-in-JS.

The core idea is to retain the CSS-in-JS developer experience while completing all the work at build time. A build tool, usually a Babel or Vite plugin, scans the code, finds every CSS-in-JS usage, and then:

  1. Extracts the CSS text.
  2. Generates static .css files.
  3. Replaces the original CSS-in-JS code with generated, unique class names.

As a result, the JavaScript bundle delivered to the browser contains no CSS-in-JS runtime library, producing an outcome equivalent to highly optimized, hand-written static CSS files.

3. Leading Zero-Runtime Solutions

a. Linaria

Linaria is one of the pioneers of zero-runtime CSS-in-JS. It lets you use the familiar styled tagged-template-literal syntax.

// YourComponent.js
import { styled } from '@linaria/react';

const Title = styled.h1`
  font-size: 2rem;
  color: tomato;
`;

// 构建时,这会被转换为:
// <h1 class="Title_a1b2c3d">...</h1>
//
// 并在一个静态 .css 文件中生成:
// .Title_a1b2c3d {
//   font-size: 2rem;
//   color: tomato;
// }

b. vanilla-extract

Developed by the team at SEEK, vanilla-extract goes a step further by using TypeScript to create fully type-safe styles. Every style definition is an exported variable in a .css.ts file, providing powerful type inference and autocompletion.

// styles.css.ts
import { style } from '@vanilla-extract/css';

export const title = style({
  fontSize: '2rem',
  color: 'tomato',
});

c. Panda CSS

Panda CSS is a newer competitor. Inspired by Tailwind CSS, it provides a “Style Props” developer experience while guaranteeing zero-runtime output.

import { css } from '../styled-system/css';

function MyComponent() {
  return <div className={css({ fontSize: '2rem', color: 'tomato' })} />;
}

4. Advantages and Trade-Offs

Advantages:

  • Exceptional performance: The final output is static CSS with no JavaScript runtime overhead.
  • Smaller bundle size: There is no need to load a CSS-in-JS library on the client.
  • Powerful developer experience: You still benefit from component scope, TypeScript type safety, and code colocation.

Trade-offs:

  • Build-time dependency: The solution must be integrated into the build process, making configuration somewhat more complex.
  • Limited dynamism: It cannot generate entirely new styles from values available only at runtime, such as data from an API. However, techniques such as CSS variables can cover most dynamic scenarios.

Conclusion

Zero-runtime CSS-in-JS is a course correction for the traditional CSS-in-JS paradigm. It cleverly separates the “development-time experience” from “runtime performance,” so we no longer have to make a difficult compromise between them. For modern web applications pursuing exceptional performance and smaller bundle sizes, it offers an almost perfect solution.