What Is Zero-Runtime CSS-in-JS?

Zero-runtime CSS-in-JS libraries combine the developer ergonomics of writing styles within JavaScript or TypeScript with the performance advantages of traditional, static CSS stylesheets. This article explains how zero-runtime libraries operate, why they were developed to solve the performance bottlenecks of traditional runtime styling tools, and how popular solutions like Linaria and Vanilla Extract implement this approach.

The Problem with Traditional Runtime CSS-in-JS

Traditional CSS-in-JS libraries, such as earlier versions of styled-components and Emotion, process and inject styles dynamically in the user's browser during execution. When a component mounts or dynamic props change, the runtime library computes styles, generates unique class names, and inserts new <style> tags directly into the document's <head>.

While this model provides great flexibility and component-level scoping, it introduces noticeable drawbacks:

  • Bundle Bloat: The styling library’s runtime parsing and injection engine must be shipped to the client inside the JavaScript bundle.
  • Performance Overhead: The browser spends CPU cycles parsing, hashing, and injecting styles instead of rendering UI.
  • React Server Components (RSC) Incompatibility: Modern React architectures cannot easily support dynamic runtime style injection from server-only components.

What Does "Zero-Runtime" Mean?

"Zero-runtime" means that no styling engine or JavaScript interpreter is shipped to the client to generate CSS. Instead, the styling code is evaluated, extracted, and compiled into standard .css files entirely during the build step using bundler plugins (such as Vite, Webpack, or esbuild).

At build time, the compiler performs the following tasks:

  1. Parses the styling declarations written in JavaScript or TypeScript.
  2. Generates static CSS rules and saves them into standard .css files.
  3. Replaces the JavaScript styling definitions in the source code with simple, static string class names.

The browser receives plain CSS files that can be cached and loaded in parallel with JavaScript, completely eliminating runtime overhead.

Linaria

Linaria was one of the earliest zero-runtime solutions. It preserves the familiar syntax of styled-components, allowing developers to write CSS within JavaScript using tagged template literals.

Linaria uses Babel or bundler plugins to evaluate CSS rules at build time. Dynamic styles that depend on runtime props cannot be computed statically; instead, Linaria automatically transforms dynamic values into CSS custom properties (CSS variables) attached to inline styles. This ensures that the bulk of the CSS remains static while still supporting dynamic UI updates.

Vanilla Extract

Vanilla Extract represents a modern, TypeScript-first approach to zero-runtime CSS. Unlike Linaria, Vanilla Extract requires styles to be written in dedicated styling files (e.g., theme.css.ts) using plain TypeScript objects rather than template literals.

Key characteristics of Vanilla Extract include:

  • Strict Type Safety: Themes, styles, and property values are validated at compile time, eliminating typos and unsupported CSS rules.
  • No Custom Runtime in Production: Code inside .css.ts files runs entirely inside the build engine (via Node.js/esbuild). Only hashed class names are imported into component files.
  • First-Class Theme Contracts: It enforces structured design systems by separating theme contracts from specific implementations, allowing easy theme switching via CSS variables.

Key Advantages and Limitations

Advantages:

  • Optimal Web Performance: Styles benefit from standard browser caching and parallel loading alongside HTML and JS.
  • Server-Side Rendering and RSC Compatibility: Because there is no client-side runtime dependency, zero-runtime styles work out of the box with modern frameworks like Next.js, Remix, and Astro.
  • Developer Experience: Developers retain the benefits of colocation, scoping, and type-checked design tokens without sacrificing client performance.

Limitations:

  • Dynamic Prop Constraints: You cannot run arbitrary JavaScript calculations on every render to change CSS rules; dynamic behavior must be mapped to class toggles or CSS custom properties.
  • Build-Tool Dependency: Setup requires deeper integration with build tools, bundlers, and compilation pipelines.